diff --git a/.claude/.pre-pr-sweep-state b/.claude/.pre-pr-sweep-state new file mode 100644 index 0000000000..f6f2d8d645 --- /dev/null +++ b/.claude/.pre-pr-sweep-state @@ -0,0 +1 @@ +alt-blur@3076a2b5a593b0d7b37198b5aebfe3a222afd52d diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000000..15b9369251 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "Bash(pnpm typecheck *)", + "Bash(pnpm lint *)", + "Bash(pnpm test *)" + ] + } +} diff --git a/.github/claude-review-prompt.md b/.github/claude-review-prompt.md new file mode 100644 index 0000000000..496820a8f0 --- /dev/null +++ b/.github/claude-review-prompt.md @@ -0,0 +1,63 @@ +You are an experienced senior React Native engineer reviewing a pull +request in the Bluesky Social app — a cross-platform (iOS, Android, Web) +React Native + Expo application. Read the repo's CLAUDE.md before forming +an opinion; it describes the architecture, the ALF design system, and the +codebase conventions. + +Your audience is other senior engineers. Write peer-to-peer, not +teacher-to-junior. Most PRs in this repo are fine; a review that says so +is a valid and common outcome. + +Report a finding only if you can name a concrete scenario — specific +input, platform, navigation path, or operating condition — in which the +change causes incorrect behavior, a crash, a visual regression, a test +failure, a security issue, or a real regression visible to users. Style, +naming, and micro-optimizations are out of scope unless they introduce a +defect. Do not speculate that a change "might" break unrelated code +without pointing to the specific caller or code path. Do not repeat what +the diff does. + +Where this codebase differs from a typical web app: + +- Three platforms from one codebase. Web-only APIs (DOM, window), + native-only modules, and platform-specific files (.web.tsx, .ios.tsx, + .android.tsx) are common sources of single-platform breakage. When a + change touches shared code, consider all three targets. +- User-facing strings must go through Lingui (the `Trans` macro / + `useLingui`). Hardcoded English strings in UI are a finding. Do not + flag missing translations in catalog files — extraction and + compilation run in CI. +- New UI should use ALF (`#/alf`, `#/components`) rather than legacy + patterns (`#/view/com`, StyleSheet.create); flag newly written code + that adopts deprecated patterns, but don't flag pre-existing code the + PR merely touches. +- Server state lives in TanStack Query under src/state/queries. Watch + for cache-shape changes without corresponding invalidation updates, + and optimistic updates that can leave stale cache on failure. +- List rendering is performance-critical (the main feed). Changes to + feed items, FlatList usage, or anything in a hot render path deserve + scrutiny for re-render storms — unstable callback/object identities + passed to memoized children, missing memoization on expensive + computation. +- Moderation and content-filtering logic (labels, mutes, blocks, + hidden posts) is trust-and-safety-critical: a regression that shows + content that should be filtered is a blocking finding. +- Deep links, push-notification routing, and the navigation state + machine have platform-specific edge cases; changes there should name + the platforms they were verified on. +- The embed (bskyembed) and web deployment surfaces (bskyweb, link, + ogcard services in Go) ship separately from the app; changes there + have their own blast radius. + +For each finding, state the scenario in one or two sentences, cite +file:line, and mark severity (blocking / non-blocking). If you are +uncertain but the potential impact is high (crash on startup, moderation +bypass, broken auth), include it and say what you are uncertain about. +Otherwise, prefer silence over guessing. + +If there are no findings that meet this bar, say briefly that the PR +looks fine and note what you checked. + +Post your review as a single top-level PR comment. Per-finding inline +comments are also welcome where they'd anchor a reader to the specific +lines involved. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..9404af75f7 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,44 @@ +version: 2 +# Dependabot auto-update config. +# +# Cooldown (7 days) is the point of this config: it delays version-update +# PRs until a newly-published version has aged. Supply-chain attacks like +# the tanstack Shai-Hulud compromise (2026-05-11) live minutes-to-hours +# before the registry yanks them; a 7-day cooldown keeps poisoned +# versions out of our lockfiles. +# +# Security updates bypass cooldown and continue to flow immediately. See: +# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference#cooldown +# +# Auto-merge is deliberately NOT enabled. Every dependabot PR gets human +# review. + +updates: + - package-ecosystem: npm + directory: / + schedule: + interval: weekly + day: monday + cooldown: + default-days: 7 + open-pull-requests-limit: 5 + groups: + production: + dependency-type: production + update-types: [minor, patch] + development: + dependency-type: development + update-types: [minor, patch] + + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: monday + cooldown: + default-days: 7 + open-pull-requests-limit: 3 + groups: + actions: + patterns: ["*"] + update-types: [minor, patch] diff --git a/.github/workflows/build-and-push-bskyweb-aws.yaml b/.github/workflows/build-and-push-bskyweb-aws.yaml index 45ff55810b..2c8d09c776 100644 --- a/.github/workflows/build-and-push-bskyweb-aws.yaml +++ b/.github/workflows/build-and-push-bskyweb-aws.yaml @@ -22,13 +22,13 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Setup Docker buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 - name: Log into registry ${{ env.REGISTRY }} - uses: docker/login-action@v2 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: registry: ${{ env.REGISTRY }} username: ${{ env.USERNAME}} @@ -36,7 +36,7 @@ jobs: - name: Extract Docker metadata id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 with: images: | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} @@ -51,7 +51,7 @@ jobs: - name: Build and push Docker image id: build-and-push - uses: docker/build-push-action@v4 + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 with: context: . push: ${{ github.event_name != 'pull_request' }} diff --git a/.github/workflows/build-and-push-bskyweb-ghcr.yaml b/.github/workflows/build-and-push-bskyweb-ghcr.yaml index 6a959be304..693e9a1d3b 100644 --- a/.github/workflows/build-and-push-bskyweb-ghcr.yaml +++ b/.github/workflows/build-and-push-bskyweb-ghcr.yaml @@ -23,13 +23,13 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Setup Docker buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 - name: Log into registry ${{ env.REGISTRY }} - uses: docker/login-action@v2 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: registry: ${{ env.REGISTRY }} username: ${{ env.USERNAME }} @@ -37,7 +37,7 @@ jobs: - name: Extract Docker metadata id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 with: images: | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} @@ -46,7 +46,7 @@ jobs: - name: Build and push Docker image id: build-and-push - uses: docker/build-push-action@v4 + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 with: context: . push: ${{ github.event_name != 'pull_request' }} diff --git a/.github/workflows/build-and-push-embedr-aws.yaml b/.github/workflows/build-and-push-embedr-aws.yaml index fa1e8bd7dd..a4e1bd2c88 100644 --- a/.github/workflows/build-and-push-embedr-aws.yaml +++ b/.github/workflows/build-and-push-embedr-aws.yaml @@ -22,13 +22,13 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Setup Docker buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 - name: Log into registry ${{ env.REGISTRY }} - uses: docker/login-action@v2 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: registry: ${{ env.REGISTRY }} username: ${{ env.USERNAME}} @@ -36,7 +36,7 @@ jobs: - name: Extract Docker metadata id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 with: images: | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} @@ -45,7 +45,7 @@ jobs: - name: Build and push Docker image id: build-and-push - uses: docker/build-push-action@v4 + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 with: context: . push: ${{ github.event_name != 'pull_request' }} diff --git a/.github/workflows/build-and-push-link-aws.yaml b/.github/workflows/build-and-push-link-aws.yaml index be77ff1841..31eaa8357f 100644 --- a/.github/workflows/build-and-push-link-aws.yaml +++ b/.github/workflows/build-and-push-link-aws.yaml @@ -1,11 +1,9 @@ name: build-and-push-link-aws on: workflow_dispatch: - pull_request: - paths: - - "bskylink/**" - - "Dockerfile.bskylink" - - ".github/workflows/build-and-push-link-aws.yaml" + push: + branches: + - main env: REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }} @@ -24,13 +22,13 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Setup Docker buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 - name: Log into registry ${{ env.REGISTRY }} - uses: docker/login-action@v2 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: registry: ${{ env.REGISTRY }} username: ${{ env.USERNAME}} @@ -38,7 +36,7 @@ jobs: - name: Extract Docker metadata id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 with: images: | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} @@ -47,7 +45,7 @@ jobs: - name: Build and push Docker image id: build-and-push - uses: docker/build-push-action@v4 + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 with: context: . push: true diff --git a/.github/workflows/build-and-push-ogcard-aws.yaml b/.github/workflows/build-and-push-ogcard-aws.yaml index 961768b2b1..3102b05975 100644 --- a/.github/workflows/build-and-push-ogcard-aws.yaml +++ b/.github/workflows/build-and-push-ogcard-aws.yaml @@ -1,11 +1,9 @@ name: build-and-push-ogcard-aws on: workflow_dispatch: - pull_request: - paths: - - "bskyogcard/**" - - "Dockerfile.bskyogcard" - - ".github/workflows/build-and-push-ogcard-aws.yaml" + push: + branches: + - main env: REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }} @@ -24,13 +22,13 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Setup Docker buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 - name: Log into registry ${{ env.REGISTRY }} - uses: docker/login-action@v2 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: registry: ${{ env.REGISTRY }} username: ${{ env.USERNAME}} @@ -38,7 +36,7 @@ jobs: - name: Extract Docker metadata id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 with: images: | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} @@ -47,7 +45,7 @@ jobs: - name: Build and push Docker image id: build-and-push - uses: docker/build-push-action@v4 + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 with: context: . push: true diff --git a/.github/workflows/build-submit-android.yml b/.github/workflows/build-submit-android.yml index 6deb869142..7416ca984b 100644 --- a/.github/workflows/build-submit-android.yml +++ b/.github/workflows/build-submit-android.yml @@ -11,6 +11,10 @@ on: - testflight-android - production +# Deploys happen via EAS using EXPO_TOKEN; the GITHUB_TOKEN only checks out code +permissions: + contents: read + jobs: build: if: github.repository == 'bluesky-social/social-app' @@ -28,39 +32,38 @@ jobs: fi - name: ⬇️ Checkout - uses: actions/checkout@v4 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: fetch-depth: 5 + - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + - name: 🔧 Setup Node - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version-file: .nvmrc - cache: yarn + node-version-file: package.json + cache: pnpm - name: 🪛 Setup jq - uses: dcarbone/install-jq-action@v2 + uses: dcarbone/install-jq-action@b7ef57d46ece78760b4019dbc4080a1ba2a40b45 # v3.2.0 - - name: 🔨 Setup EAS - uses: expo/expo-github-action@main + - name: ⚙️ Install dependencies + run: pnpm install --frozen-lockfile + + - name: 🔨 Setup Expo CLI + uses: expo/expo-github-action@eab7a230208c952974db8c3245cfd78402c7b385 # 9.0.0 with: - expo-version: latest - eas-version: latest + eas-version: '19.0.5' + packager: 'pnpm --allow-build=dtrace-provider' token: ${{ secrets.EXPO_TOKEN }} - - name: ⛏️ Setup EAS local builds - run: yarn global add eas-cli-local-build-plugin - - - uses: actions/setup-java@v4 + - uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0 with: distribution: "temurin" java-version: "17" - - name: ⚙️ Install dependencies - run: yarn install --frozen-lockfile - - name: 🔤 Compile translations - run: yarn intl:build 2>&1 | tee i18n.log + run: pnpm intl:build 2>&1 | tee i18n.log - name: Check for i18n compilation errors run: if grep -q "invalid syntax" "i18n.log"; then echo "\n\nFound compilation @@ -86,10 +89,12 @@ jobs: env: PROFILE: ${{ inputs.profile || 'testflight-android' }} run: > - SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_RELEASE=${{ - steps.env.outputs.EXPO_PUBLIC_RELEASE_VERSION }} SENTRY_DIST=${{ - steps.env.outputs.EXPO_PUBLIC_BUNDLE_IDENTIFIER }} yarn - use-build-number-with-bump eas build -p android --profile $PROFILE + SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_RELEASE=${{ steps.env.outputs.EXPO_PUBLIC_RELEASE_VERSION }} + SENTRY_DIST=${{ steps.env.outputs.EXPO_PUBLIC_BUNDLE_IDENTIFIER }} + pnpm use-build-number-with-bump + pnpm eas build -p android + --profile $PROFILE --local --output build.aab --non-interactive - name: 📚 Get version from package.json @@ -99,19 +104,17 @@ jobs: - name: 🚀 Submit to Google Play env: PROFILE: ${{ inputs.profile || 'testflight-android' }} - run: eas submit -p android --profile $PROFILE --non-interactive --path build.aab + run: pnpm eas submit -p android --profile $PROFILE --non-interactive --path build.aab - name: 🔔 Notify Slack of Play Store Submission if: ${{ inputs.profile == 'production' }} - uses: slackapi/slack-github-action@v1.25.0 + uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3 with: + webhook: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }} + webhook-type: incoming-webhook + payload-templated: true payload: | - { - "text": "Android ${{ inputs.profile || 'testflight-android' }} build submitted to Google Play!\n```Version Number: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}\nBuild Number: ${{ steps.get-build-info.outputs.BSKY_ANDROID_VERSION_CODE }}```" - } - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }} - SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK + {"text": "Android ${{ inputs.profile || 'testflight-android' }} build submitted to Google Play!\n```Version Number: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}\nBuild Number: ${{ steps.get-build-info.outputs.BSKY_ANDROID_VERSION_CODE }}```"} - name: 🔧 Setup bundletool uses: amyu/setup-bundletool@v1 @@ -141,13 +144,11 @@ jobs: - name: ⏰ Get a timestamp id: timestamp - uses: nanzm/get-time-action@master - with: - format: "MM-DD-HH-mm-ss" + run: echo "time=$(date -u +'%m-%d-%H-%M-%S')" >> "$GITHUB_OUTPUT" - name: 🚀 Upload APK Artifact id: upload-artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: retention-days: 30 compression-level: 6 @@ -155,19 +156,17 @@ jobs: path: build.apk - name: 🔔 Notify Slack of APK Artifact - uses: slackapi/slack-github-action@v1.25.0 + uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3 with: + webhook: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }} + webhook-type: incoming-webhook + payload-templated: true payload: | - { - "text": "Android ${{ inputs.profile || 'testflight-android' }} APK is ready for testing!\n```Artifact: ${{ steps.upload-artifact.outputs.artifact-url }}\nVersion Number: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}\nBuild Number: ${{ steps.get-build-info.outputs.BSKY_ANDROID_VERSION_CODE }}```" - } - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }} - SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK + {"text": "Android ${{ inputs.profile || 'testflight-android' }} APK is ready for testing!\n```Artifact: ${{ steps.upload-artifact.outputs.artifact-url }}\nVersion Number: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}\nBuild Number: ${{ steps.get-build-info.outputs.BSKY_ANDROID_VERSION_CODE }}```"} - name: ⬇️ Restore Cache id: get-base-commit - uses: actions/cache@v4 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 if: ${{ inputs.profile == 'testflight-android' }} with: path: most-recent-testflight-commit.txt diff --git a/.github/workflows/build-submit-ios.yml b/.github/workflows/build-submit-ios.yml index 2b316895e7..d4cd6109ea 100644 --- a/.github/workflows/build-submit-ios.yml +++ b/.github/workflows/build-submit-ios.yml @@ -11,6 +11,10 @@ on: - testflight - production +# Deploys happen via EAS using EXPO_TOKEN; the GITHUB_TOKEN only checks out code +permissions: + contents: read + jobs: build: if: github.repository == 'bluesky-social/social-app' @@ -28,52 +32,51 @@ jobs: fi - name: ⬇️ Checkout - uses: actions/checkout@v4 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: fetch-depth: 5 + - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + - name: 🔧 Setup Node - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version-file: .nvmrc - cache: yarn + node-version-file: package.json + cache: pnpm - name: 🪛 Setup jq - uses: dcarbone/install-jq-action@v2 - - - name: 🔨 Setup EAS - uses: expo/expo-github-action@main - with: - expo-version: latest - eas-version: latest - token: ${{ secrets.EXPO_TOKEN }} - - - name: ⛏️ Setup EAS local builds - run: yarn global add eas-cli-local-build-plugin + uses: dcarbone/install-jq-action@b7ef57d46ece78760b4019dbc4080a1ba2a40b45 # v3.2.0 - name: ⚙️ Install dependencies - run: yarn install --frozen-lockfile + run: pnpm install --frozen-lockfile - - uses: maxim-lobanov/setup-xcode@v1 + - name: 🔨 Setup Expo CLI + uses: expo/expo-github-action@eab7a230208c952974db8c3245cfd78402c7b385 # 9.0.0 + with: + eas-version: '19.0.5' + packager: 'pnpm --allow-build=dtrace-provider' + token: ${{ secrets.EXPO_TOKEN }} + + - uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 with: xcode-version: "26.4" - name: ☕️ Setup Cocoapods - uses: maxim-lobanov/setup-cocoapods@v1 + uses: maxim-lobanov/setup-cocoapods@8e97e1e98e6ccf42564fdf5622c8feec74199377 # v1.4.0 with: version: 1.16.2 - name: 💾 Cache Pods - uses: actions/cache@v4 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 id: pods-cache with: path: ./ios/Pods - # We'll use the yarn.lock for our hash since we don't yet have a Podfile.lock. Pod versions will not - # change unless the yarn version changes as well. - key: ${{ runner.os }}-pods-${{ hashFiles('yarn.lock') }} + # We'll use the pnpm-lock.yaml for our hash since we don't yet have a Podfile.lock. Pod versions will not + # change unless the pnpm version changes as well. + key: ${{ runner.os }}-pods-${{ hashFiles('pnpm-lock.yaml') }} - name: 🔤 Compile translations - run: yarn intl:build 2>&1 | tee i18n.log + run: pnpm intl:build 2>&1 | tee i18n.log - name: Check for i18n compilation errors run: if grep -q "invalid syntax" "i18n.log"; then echo "\n\nFound compilation @@ -98,11 +101,13 @@ jobs: env: PROFILE: ${{ inputs.profile || 'testflight' }} run: > - SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_RELEASE=${{ - steps.env.outputs.EXPO_PUBLIC_RELEASE_VERSION }} SENTRY_DIST=${{ - steps.env.outputs.EXPO_PUBLIC_BUNDLE_IDENTIFIER }} yarn - use-build-number-with-bump eas build -p ios --profile $PROFILE --local - --output build.tar.gz --non-interactive + SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_RELEASE=${{ steps.env.outputs.EXPO_PUBLIC_RELEASE_VERSION }} + SENTRY_DIST=${{ steps.env.outputs.EXPO_PUBLIC_BUNDLE_IDENTIFIER }} + pnpm use-build-number-with-bump + pnpm eas build -p ios + --profile $PROFILE + --local --output build.tar.gz --non-interactive - name: 📂 Extract build artifact run: | @@ -140,13 +145,14 @@ jobs: fi - name: 🚀 Deploy - run: eas submit -p ios --non-interactive --path "$BUILD_DIR/Bluesky.ipa" + run: pnpm eas submit -p ios --non-interactive --path "$BUILD_DIR/Bluesky.ipa" - name: 🪲 Upload dSYM to Sentry run: > - SENTRY_ORG=blueskyweb SENTRY_PROJECT=app SENTRY_AUTH_TOKEN=${{ - secrets.SENTRY_AUTH_TOKEN }} yarn sentry-cli debug-files upload - "$BUILD_DIR/Bluesky.app.dSYM.zip" --include-sources + SENTRY_ORG=blueskyweb + SENTRY_PROJECT=app + SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} + pnpm sentry-cli debug-files upload "$BUILD_DIR/Bluesky.app.dSYM.zip" --include-sources - name: 📚 Get version from package.json id: get-build-info @@ -154,19 +160,17 @@ jobs: - name: 🔔 Notify Slack of Production Build if: ${{ inputs.profile == 'production' }} - uses: slackapi/slack-github-action@v1.25.0 + uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3 with: + webhook: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }} + webhook-type: incoming-webhook + payload-templated: true payload: | - { - "text": "iOS production build for App Store submission is ready!\n```Artifact: Check TestFlight to know when it is available\nVersion Number: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}\nBuild Number: ${{ steps.get-build-info.outputs.BSKY_IOS_BUILD_NUMBER }}```" - } - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }} - SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK + {"text": "iOS production build for App Store submission is ready!\n```Artifact: Check TestFlight to know when it is available\nVersion Number: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}\nBuild Number: ${{ steps.get-build-info.outputs.BSKY_IOS_BUILD_NUMBER }}```"} - name: ⬇️ Restore Cache id: get-base-commit - uses: actions/cache@v4 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 if: ${{ inputs.profile == 'testflight' }} with: path: most-recent-testflight-commit.txt diff --git a/.github/workflows/bundle-deploy-eas-update.yml b/.github/workflows/bundle-deploy-eas-update.yml index 5bf17f90b4..37e9f0a69f 100644 --- a/.github/workflows/bundle-deploy-eas-update.yml +++ b/.github/workflows/bundle-deploy-eas-update.yml @@ -18,6 +18,10 @@ on: description: Runtime version (in x.x.x format) that this update is for required: true +# Deploys happen via EAS using EXPO_TOKEN; the GITHUB_TOKEN only checks out code +permissions: + contents: read + jobs: bundleDeploy: if: github.repository == 'bluesky-social/social-app' @@ -48,7 +52,7 @@ jobs: fi - name: ⬇️ Checkout - uses: actions/checkout@v4 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: fetch-depth: 0 @@ -56,53 +60,48 @@ jobs: if: ${{ github.ref != 'refs/heads/main' }} run: git fetch origin main:main --depth 100 + - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + - name: 🔧 Setup Node - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version-file: .nvmrc - cache: yarn + node-version-file: package.json + cache: pnpm - name: 📷 Check fingerprint and install dependencies id: fingerprint - uses: bluesky-social/github-actions/fingerprint-native@main + uses: bluesky-social/github-actions/fingerprint-native@ebc6aa6d7466dc1e78b1e832041b7b81f6f95030 # v0.1.0 with: profile: ${{ inputs.channel || 'testflight' }} previous-commit-tag: ${{ inputs.runtimeVersion }} - - name: Lint check - run: yarn lint - - - name: Lint lockfile - run: yarn lockfile-lint - - - name: Prettier check - run: yarn prettier --check . - - name: 🔤 Compile translations - run: yarn intl:build 2>&1 | tee i18n.log + run: pnpm intl:build 2>&1 | tee i18n.log - name: Check for i18n compilation errors run: if grep -q "invalid syntax" "i18n.log"; then echo "\n\nFound compilation errors!\n\n" && exit 1; else echo "\n\nNo compilation errors!\n\n"; fi + - name: Lint check + run: pnpm lint + + - name: Prettier check + run: pnpm prettier --check . + - name: Type check - run: yarn typecheck + run: pnpm typecheck - name: 🔨 Setup EAS - uses: expo/expo-github-action@main + uses: expo/expo-github-action@eab7a230208c952974db8c3245cfd78402c7b385 # 9.0.0 if: ${{ !steps.fingerprint.outputs.includes-changes }} with: - expo-version: latest - eas-version: latest + eas-version: '19.0.5' + packager: 'pnpm --allow-build=dtrace-provider' token: ${{ secrets.EXPO_TOKEN }} - - name: ⛏️ Setup Expo - if: ${{ !steps.fingerprint.outputs.includes-changes }} - run: yarn global add eas-cli-local-build-plugin - - name: 🪛 Setup jq if: ${{ !steps.fingerprint.outputs.includes-changes }} - uses: dcarbone/install-jq-action@v2 + uses: dcarbone/install-jq-action@b7ef57d46ece78760b4019dbc4080a1ba2a40b45 # v3.2.0 # eas.json not used here, set EXPO_PUBLIC_ENV - name: Env @@ -128,13 +127,14 @@ jobs: - name: 🏗️ Create Bundle if: ${{ !steps.fingerprint.outputs.includes-changes }} run: > - SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_RELEASE=${{ - steps.env.outputs.EXPO_PUBLIC_RELEASE_VERSION }} SENTRY_DIST=${{ - steps.env.outputs.EXPO_PUBLIC_BUNDLE_IDENTIFIER }} yarn export + SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_RELEASE=${{ steps.env.outputs.EXPO_PUBLIC_RELEASE_VERSION }} + SENTRY_DIST=${{ steps.env.outputs.EXPO_PUBLIC_BUNDLE_IDENTIFIER }} + pnpm export - name: 📦 Package Bundle and 🚀 Deploy if: ${{ !steps.fingerprint.outputs.includes-changes }} - run: yarn use-build-number bash scripts/bundleUpdate.sh + run: pnpm use-build-number bash scripts/bundleUpdate.sh env: DENIS_API_KEY: ${{ secrets.DENIS_API_KEY }} RUNTIME_VERSION: ${{ inputs.runtimeVersion }} @@ -142,7 +142,7 @@ jobs: - name: ⬇️ Restore Cache id: get-base-commit - uses: actions/cache@v4 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 if: ${{ !steps.fingerprint.outputs.includes-changes }} with: path: most-recent-testflight-commit.txt @@ -174,49 +174,48 @@ jobs: fi - name: ⬇️ Checkout - uses: actions/checkout@v4 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: fetch-depth: 5 + - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + - name: 🔧 Setup Node - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version-file: .nvmrc - cache: yarn + node-version-file: package.json + cache: pnpm - name: 🔨 Setup EAS - uses: expo/expo-github-action@main + uses: expo/expo-github-action@eab7a230208c952974db8c3245cfd78402c7b385 # 9.0.0 with: - expo-version: latest - eas-version: latest + eas-version: '19.0.5' + packager: 'pnpm --allow-build=dtrace-provider' token: ${{ secrets.EXPO_TOKEN }} - - name: ⛏️ Setup EAS local builds - run: yarn global add eas-cli-local-build-plugin - - name: ⚙️ Install dependencies - run: yarn install --frozen-lockfile + run: pnpm install --frozen-lockfile - - uses: maxim-lobanov/setup-xcode@v1 + - uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 with: xcode-version: "26.4" - name: ☕️ Setup Cocoapods - uses: maxim-lobanov/setup-cocoapods@v1 + uses: maxim-lobanov/setup-cocoapods@8e97e1e98e6ccf42564fdf5622c8feec74199377 # v1.4.0 with: version: 1.16.2 - name: 💾 Cache Pods - uses: actions/cache@v4 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 id: pods-cache with: path: ./ios/Pods - # We'll use the yarn.lock for our hash since we don't yet have a Podfile.lock. Pod versions will not - # change unless the yarn version changes as well. - key: ${{ runner.os }}-pods-${{ hashFiles('yarn.lock') }} + # We'll use the pnpm-lock.yaml for our hash since we don't yet have a Podfile.lock. Pod versions will not + # change unless the pnpm version changes as well. + key: ${{ runner.os }}-pods-${{ hashFiles('pnpm-lock.yaml') }} - name: 🔤 Compile translations - run: yarn intl:build + run: pnpm intl:build # EXPO_PUBLIC_ENV is handled in eas.json - name: Env @@ -235,10 +234,12 @@ jobs: - name: 🏗️ EAS Build run: > - SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_RELEASE=${{ - steps.env.outputs.EXPO_PUBLIC_RELEASE_VERSION }} SENTRY_DIST=${{ - steps.env.outputs.EXPO_PUBLIC_BUNDLE_IDENTIFIER }} yarn - use-build-number-with-bump eas build -p ios --profile testflight + SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_RELEASE=${{ steps.env.outputs.EXPO_PUBLIC_RELEASE_VERSION }} + SENTRY_DIST=${{ steps.env.outputs.EXPO_PUBLIC_BUNDLE_IDENTIFIER }} + pnpm use-build-number-with-bump + pnpm eas build -p ios + --profile testflight --local --output build.tar.gz --non-interactive - name: 📂 Extract build artifact @@ -277,17 +278,18 @@ jobs: fi - name: 🚀 Deploy - run: eas submit -p ios --non-interactive --path "$BUILD_DIR/Bluesky.ipa" + run: pnpm eas submit -p ios --non-interactive --path "$BUILD_DIR/Bluesky.ipa" - name: 🪲 Upload dSYM to Sentry run: > - SENTRY_ORG=blueskyweb SENTRY_PROJECT=app SENTRY_AUTH_TOKEN=${{ - secrets.SENTRY_AUTH_TOKEN }} yarn sentry-cli debug-files upload - "$BUILD_DIR/Bluesky.app.dSYM.zip" --include-sources + SENTRY_ORG=blueskyweb + SENTRY_PROJECT=app + SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} + pnpm sentry-cli debug-files upload "$BUILD_DIR/Bluesky.app.dSYM.zip" --include-sources - name: ⬇️ Restore Cache id: get-base-commit - uses: actions/cache@v4 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 if: ${{ inputs.channel == 'testflight' }} with: path: most-recent-testflight-commit.txt @@ -321,36 +323,35 @@ jobs: fi - name: ⬇️ Checkout - uses: actions/checkout@v4 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: fetch-depth: 5 + - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + - name: 🔧 Setup Node - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version-file: .nvmrc - cache: yarn + node-version-file: package.json + cache: pnpm - name: 🔨 Setup EAS - uses: expo/expo-github-action@main + uses: expo/expo-github-action@eab7a230208c952974db8c3245cfd78402c7b385 # 9.0.0 with: - expo-version: latest - eas-version: latest + eas-version: '19.0.5' + packager: 'pnpm --allow-build=dtrace-provider' token: ${{ secrets.EXPO_TOKEN }} - - name: ⛏️ Setup EAS local builds - run: yarn global add eas-cli-local-build-plugin - - - uses: actions/setup-java@v4 + - uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0 with: distribution: "temurin" java-version: "17" - name: ⚙️ Install dependencies - run: yarn install --frozen-lockfile + run: pnpm install --frozen-lockfile - name: 🔤 Compile translations - run: yarn intl:build + run: pnpm intl:build # EXPO_PUBLIC_ENV is handled in eas.json - name: Env @@ -370,18 +371,20 @@ jobs: - name: 🏗️ EAS Build run: > - SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_RELEASE=${{ - steps.env.outputs.EXPO_PUBLIC_RELEASE_VERSION }} SENTRY_DIST=${{ - steps.env.outputs.EXPO_PUBLIC_BUNDLE_IDENTIFIER }} yarn - use-build-number-with-bump eas build -p android --profile - testflight-android --local --output build.aab --non-interactive + SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_RELEASE=${{ steps.env.outputs.EXPO_PUBLIC_RELEASE_VERSION }} + SENTRY_DIST=${{ steps.env.outputs.EXPO_PUBLIC_BUNDLE_IDENTIFIER }} + pnpm use-build-number-with-bump + pnpm eas build -p android + --profile testflight-android + --local --output build.aab --non-interactive - name: 📚 Get version from package.json id: get-build-info run: bash scripts/setGitHubOutput.sh - name: 🚀 Submit to Google Play - run: eas submit -p android --profile testflight-android --non-interactive --path + run: pnpm eas submit -p android --profile testflight-android --non-interactive --path build.aab - name: 🔧 Setup bundletool @@ -412,13 +415,11 @@ jobs: - name: ⏰ Get a timestamp id: timestamp - uses: nanzm/get-time-action@master - with: - format: "MM-DD-HH-mm-ss" + run: echo "time=$(date -u +'%m-%d-%H-%M-%S')" >> "$GITHUB_OUTPUT" - name: 🚀 Upload Artifact id: upload-artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: retention-days: 30 compression-level: 0 @@ -426,19 +427,17 @@ jobs: path: build.apk - name: 🔔 Notify Slack - uses: slackapi/slack-github-action@v1.25.0 + uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3 with: + webhook: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }} + webhook-type: incoming-webhook + payload-templated: true payload: | - { - "text": "Android build is ready for testing. Download the artifact here: ${{ steps.upload-artifact.outputs.artifact-url }}" - } - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }} - SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK + {"text": "Android build is ready for testing. Download the artifact here: ${{ steps.upload-artifact.outputs.artifact-url }}"} - name: ⬇️ Restore Cache id: get-base-commit - uses: actions/cache@v4 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 if: ${{ inputs.channel != 'testflight' && inputs.channel != 'production' }} with: path: most-recent-testflight-commit.txt diff --git a/.github/workflows/claude-mention.yml b/.github/workflows/claude-mention.yml new file mode 100644 index 0000000000..5a7a7accff --- /dev/null +++ b/.github/workflows/claude-mention.yml @@ -0,0 +1,76 @@ +name: claude-mention + +# @claude mention handler on PR conversation comments, inline review +# comments, and review bodies. See the header comment in +# claude-review.yml for why this is self-contained rather than calling +# the org reusable workflows. +# +# Authorization note: this repo is public, and the load-bearing gate +# against drive-by commenters is INSIDE claude-code-action — the action +# verifies via the API that the triggering actor has write permission +# before doing anything (`allowed_non_write_users` and `allowed_bots` +# both default to deny). The `if:` below is a cheap pre-filter to avoid +# spinning up runners for the 99% of comments that don't mention +# @claude; it is not the security boundary. + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + pull_request_review: + types: [submitted] + +permissions: + contents: read + pull-requests: write + issues: write + actions: read + id-token: write + +jobs: + mention: + # Skip comments/reviews that don't mention @claude, non-PR issue + # comments, and events from claude[bot] itself (its review prose + # often quotes "@claude" and must not retrigger the workflow). + if: > + ( + (github.event_name == 'issue_comment' && + github.event.issue.pull_request != null && + contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review_comment' && + contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review' && + contains(github.event.review.body, '@claude')) + ) && + github.actor != 'claude[bot]' + + runs-on: ubuntu-latest + timeout-minutes: 20 + + concurrency: + group: claude-mention-${{ github.repository }}-${{ github.event.issue.number || github.event.pull_request.number }} + cancel-in-progress: false + + steps: + - name: Checkout repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + fetch-depth: 1 + + - name: Configure AWS credentials (OIDC) + uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6.2.0 + with: + role-to-assume: ${{ secrets.AWS_BEDROCK_REVIEW_ROLE_ARN }} + aws-region: us-east-2 + + - name: Claude + uses: anthropics/claude-code-action@4d7e1f0cd85743fdc93b1c8040ab54395da024e2 # v1.0.149 + with: + use_bedrock: 'true' + additional_permissions: | + actions: read + track_progress: true + claude_args: | + --model global.anthropic.claude-opus-4-8 + --allowedTools "mcp__github_inline_comment__create_inline_comment,mcp__github_ci__get_ci_status,mcp__github_ci__download_job_log,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)" diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml new file mode 100644 index 0000000000..3f6078219c --- /dev/null +++ b/.github/workflows/claude-review.yml @@ -0,0 +1,68 @@ +name: claude-review + +# Automatic Claude review on PR creation/update, via Bedrock (OIDC, no +# long-lived tokens). Self-contained: this intentionally uses upstream +# claude-code-action defaults rather than the org reusable workflows in +# bluesky-social/.github (which a public repo cannot call, and whose +# customizations added no value over upstream). +# +# Review guidance lives in .github/claude-review-prompt.md. + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +permissions: + contents: read + pull-requests: write + actions: read + id-token: write + +jobs: + review: + # Internal PRs only. This repo is public: fork PRs are the bulk of + # community traffic and MUST NOT trigger reviews (no Bedrock spend on + # unvetted code, and fork PRs can't mint the OIDC token anyway — + # belt-and-braces with this explicit guard). Branch PRs can only be + # created by people with write access, i.e. org members. + # Bot-authored PRs (dependabot, changesets) are also skipped. + if: > + github.event.pull_request.draft == false && + github.event.pull_request.head.repo.full_name == github.repository && + github.event.pull_request.user.type != 'Bot' + + runs-on: ubuntu-latest + timeout-minutes: 20 + + concurrency: + group: claude-review-${{ github.repository }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + + steps: + - name: Checkout repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + fetch-depth: 1 + + - name: Configure AWS credentials (OIDC) + uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6.2.0 + with: + role-to-assume: ${{ secrets.AWS_BEDROCK_REVIEW_ROLE_ARN }} + aws-region: us-east-2 + + - name: Claude review + uses: anthropics/claude-code-action@4d7e1f0cd85743fdc93b1c8040ab54395da024e2 # v1.0.149 + with: + use_bedrock: 'true' + additional_permissions: | + actions: read + track_progress: true + claude_args: | + --model global.anthropic.claude-opus-4-8 + --allowedTools "mcp__github_inline_comment__create_inline_comment,mcp__github_ci__get_ci_status,mcp__github_ci__download_job_log,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)" + prompt: | + REPO: ${{ github.repository }} + PR NUMBER: ${{ github.event.pull_request.number }} + + Read .github/claude-review-prompt.md in the checked-out repo + and review this pull request following its guidance. diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml deleted file mode 100644 index cca7b91c52..0000000000 --- a/.github/workflows/claude.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Claude Code - -on: - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - issues: - types: [opened, assigned] - pull_request_review: - types: [submitted] - -jobs: - claude: - if: | - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || - (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read - issues: read - id-token: write - actions: read # Required for Claude to read CI results on PRs - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Run Claude Code - id: claude - uses: anthropics/claude-code-action@v1 - with: - claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - - # This is an optional setting that allows Claude to read CI results on PRs - additional_permissions: | - actions: read - - # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it. - # prompt: 'Update the pull request description to include a summary of changes.' - - # Optional: Add claude_args to customize behavior and configuration - # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md - # or https://code.claude.com/docs/en/cli-reference for available options - # claude_args: '--allowed-tools Bash(gh pr:*)' - - # NOTE(sfn): we can add a custom system prompt here - - claude_args: | - --model claude-opus-4-7 diff --git a/.github/workflows/golang-test-lint.yml b/.github/workflows/golang-test-lint.yml index 6531694d5c..d61f0c153d 100644 --- a/.github/workflows/golang-test-lint.yml +++ b/.github/workflows/golang-test-lint.yml @@ -10,16 +10,20 @@ concurrency: group: "${{ github.workflow }}-${{ github.head_ref || github.ref }}" cancel-in-progress: true +permissions: + contents: read + jobs: build-and-test: runs-on: ubuntu-latest steps: - name: Git Checkout - uses: actions/checkout@v5 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Set up Go tooling - uses: actions/setup-go@v6 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: bskyweb/go.mod + cache-dependency-path: bskyweb/go.sum - name: Dummy Static Files run: touch bskyweb/static/js/blah.js && touch bskyweb/static/css/blah.txt && touch bskyweb/static/media/blah.txt - name: Check @@ -32,11 +36,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Git Checkout - uses: actions/checkout@v5 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Set up Go tooling - uses: actions/setup-go@v6 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: bskyweb/go.mod + cache-dependency-path: bskyweb/go.sum - name: Dummy Static Files run: touch bskyweb/static/js/blah.js && touch bskyweb/static/css/blah.txt && touch bskyweb/static/media/blah.txt - name: Lint diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6d6ee7eddd..5b40ad541d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -6,56 +6,114 @@ on: branches: - main concurrency: - group: "${{ github.workflow }}-${{ github.head_ref || github.ref }}" + group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}' cancel-in-progress: true +# These jobs only check out the repo and run checks, so read access to the +# repo contents is all the GITHUB_TOKEN needs. +permissions: + contents: read + jobs: linting: name: Run linters runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + job: [lint, prettier, typecheck] steps: - name: Check out Git repository - uses: actions/checkout@v4 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - name: Verify Node version pins match package.json + run: | + set -euo pipefail + expected=$(node -p "require('./package.json').engines.node.replace(/[^0-9.]/g, '')") + rc=0 + check() { + if [ "$2" != "$expected" ]; then + echo "::error file=$1::Node version mismatch: $1 pins '$2' but package.json is '$expected'" + rc=1 + fi + } + # FROM node:X.Y.Z - service runtime images + for f in Dockerfile.bskylink Dockerfile.bskyogcard; do + v=$(grep -oE 'FROM node:[0-9]+\.[0-9]+\.[0-9]+' "$f" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | sort -u) + check "$f" "$v" + done + # ENV NODE_VERSION=X.Y.Z - Go images that nvm-install Node for the JS build stage + for f in Dockerfile.embedr; do + v=$(grep -oE 'NODE_VERSION=[0-9]+\.[0-9]+\.[0-9]+' "$f" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | sort -u) + check "$f" "$v" + done + # eas.json "node": "X.Y.Z" + v=$(grep -oE '"node":[[:space:]]*"[0-9]+\.[0-9]+\.[0-9]+"' eas.json | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | sort -u) + check "eas.json" "$v" + exit $rc + - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 - name: Install node - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version-file: .nvmrc - cache: yarn - - name: Yarn install - uses: Wandalen/wretry.action@master - with: - command: yarn --frozen-lockfile - attempt_limit: 3 - attempt_delay: 2000 - - name: Lint check - run: yarn lint - - name: Lint lockfile - run: yarn lockfile-lint - - name: Prettier check - run: yarn prettier --check . + node-version-file: package.json + cache: pnpm + - name: pnpm install + run: pnpm install --frozen-lockfile - name: Check & compile i18n - run: yarn intl:build - - name: Type check - run: yarn typecheck + run: pnpm intl:build + - name: Lint checks + run: pnpm ${{ matrix.job }} + # Aggregates the matrix results into a single stable check name so branch + # protection can require "Run linters" regardless of how many matrix jobs run. + # The result is asserted in `run` (not `if`) so a malformed expression can + # never silently skip the check and report a false pass. + linting-summary: + name: Run linters + if: always() + needs: [linting] + runs-on: ubuntu-latest + steps: + - name: Require linting to have succeeded + env: + RESULT: ${{ needs.linting.result }} + run: | + echo "linting result: $RESULT" + test "$RESULT" = "success" testing: name: Run tests runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + shard: [1, 2, 3, 4] steps: - name: Check out Git repository - uses: actions/checkout@v4 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 - name: Install node - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version-file: .nvmrc - cache: yarn - - name: Yarn install - uses: Wandalen/wretry.action@master - with: - command: yarn --frozen-lockfile - attempt_limit: 3 - attempt_delay: 2000 + node-version-file: package.json + cache: pnpm + - name: pnpm install + run: pnpm install --frozen-lockfile - name: Check & compile i18n - run: yarn intl:build + run: pnpm intl:build - name: Run tests run: | - NODE_ENV=test yarn test --forceExit + NODE_ENV=test pnpm test --forceExit --shard=${{ matrix.shard }}/${{ strategy.job-total }} + # Aggregates the sharded test results into a single stable check name so branch + # protection can require "Run tests" regardless of how many shards run. + # The result is asserted in `run` (not `if`) so a malformed expression can + # never silently skip the check and report a false pass. + testing-summary: + name: Run tests + if: always() + needs: [testing] + runs-on: ubuntu-latest + steps: + - name: Require testing to have succeeded + env: + RESULT: ${{ needs.testing.result }} + run: | + echo "testing result: $RESULT" + test "$RESULT" = "success" diff --git a/.github/workflows/nightly-update-source-languages.yaml b/.github/workflows/nightly-update-source-languages.yaml index 42112c24c4..e6094476a6 100644 --- a/.github/workflows/nightly-update-source-languages.yaml +++ b/.github/workflows/nightly-update-source-languages.yaml @@ -16,29 +16,26 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v4 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: ssh-key: ${{secrets.GH_ACTION_DEPLOY_KEY}} + - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 - name: Install node - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version-file: .nvmrc - cache: yarn - - name: Yarn install - uses: Wandalen/wretry.action@master - with: - command: yarn --frozen-lockfile - attempt_limit: 3 - attempt_delay: 2000 + node-version-file: package.json + cache: pnpm + - name: pnpm install + run: pnpm install --frozen-lockfile - name: Extract language strings - run: yarn intl:extract + run: pnpm intl:extract - name: Create commit - uses: stefanzweifel/git-auto-commit-action@v5 + uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0 with: commit_message: Nightly source-language update file_pattern: ./src/locale/locales/en/messages.po - name: Push source lang to Crowdin - uses: crowdin/github-action@v2 + uses: crowdin/github-action@52aa776766211d83d975df51f3b9c53c2f8ba35f # v2.16.3 with: upload_sources: true upload_sources_args: "-b main" diff --git a/.github/workflows/pull-request-comment.yml b/.github/workflows/pull-request-comment.yml index 5668854f0a..4f152f96f1 100644 --- a/.github/workflows/pull-request-comment.yml +++ b/.github/workflows/pull-request-comment.yml @@ -5,16 +5,15 @@ on: issue_comment: types: [created] -# Permissiosn to make comments in the pull request -permissions: - pull-requests: write - actions: write - contents: read +# Permissions are granted per-job below; anything unlisted defaults to none +permissions: {} jobs: handle-comment: if: github.event.issue.pull_request runs-on: ubuntu-latest + permissions: + contents: read outputs: should-deploy: ${{ steps.check-org.outputs.result }} @@ -45,10 +44,10 @@ jobs: echo "mentioned=false" >> $GITHUB_OUTPUT fi - - name: Check organization membership + - name: Check commenter has write access if: steps.check-mention.outputs.mentioned == 'true' id: check-org - uses: actions/github-script@v7 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | try { @@ -72,13 +71,16 @@ jobs: runs-on: ubuntu-latest needs: [handle-comment] if: needs.handle-comment.outputs.should-deploy == 'true' + permissions: + contents: read + pull-requests: write steps: - name: Get PR HEAD SHA env: ISSUE_NUMBER: ${{ github.event.issue.number }} id: pr-info - uses: actions/github-script@v7 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const pr = await github.rest.pulls.get({ @@ -87,6 +89,16 @@ jobs: pull_number: process.env.ISSUE_NUMBER, }); + // This workflow runs with repo secrets in scope, so never build + // code from a fork: the commenter authorizes the deploy, but a + // fork controls what code would run during it + const expected = `${context.repo.owner}/${context.repo.repo}`; + const head = pr.data.head.repo?.full_name; + if (head !== expected) { + core.setFailed(`OTA deploys are only allowed for branches in ${expected}, not forks (got ${head})`); + return; + } + console.log(`PR HEAD SHA: ${pr.data.head.sha}`); console.log(`PR HEAD REF: ${pr.data.head.ref}`); @@ -94,7 +106,7 @@ jobs: core.setOutput('head-ref', pr.data.head.ref); - name: 💬 Drop a comment - uses: marocchino/sticky-pull-request-comment@v2 + uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 with: header: pull-request-eas-build-${{ steps.pr-info.outputs.head-sha }} number: ${{ github.event.issue.number }} @@ -113,46 +125,42 @@ jobs: fi - name: ⬇️ Checkout - uses: actions/checkout@v4 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: ref: ${{ steps.pr-info.outputs.head-sha }} + - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + - name: 🔧 Setup Node - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version-file: .nvmrc - cache: yarn + node-version-file: package.json + cache: pnpm - name: Install dependencies - run: yarn install --frozen-lockfile - - - name: Lint check - run: yarn lint - - - name: Lint lockfile - run: yarn lockfile-lint + run: pnpm install --frozen-lockfile - name: 🔤 Compile translations - run: yarn intl:build 2>&1 | tee i18n.log + run: pnpm intl:build 2>&1 | tee i18n.log - name: Check for i18n compilation errors run: if grep -q "invalid syntax" "i18n.log"; then echo "\n\nFound compilation errors!\n\n" && exit 1; else echo "\n\nNo compilation errors!\n\n"; fi + - name: Lint check + run: pnpm lint + - name: Type check - run: yarn typecheck + run: pnpm typecheck - name: 🔨 Setup EAS - uses: expo/expo-github-action@main + uses: expo/expo-github-action@eab7a230208c952974db8c3245cfd78402c7b385 # 9.0.0 with: - expo-version: latest - eas-version: latest + eas-version: '19.0.5' + packager: 'pnpm --allow-build=dtrace-provider' token: ${{ secrets.EXPO_TOKEN }} - - name: ⛏️ Setup Expo - run: yarn global add eas-cli-local-build-plugin - - name: 🪛 Setup jq - uses: dcarbone/install-jq-action@v2 + uses: dcarbone/install-jq-action@b7ef57d46ece78760b4019dbc4080a1ba2a40b45 # v3.2.0 - name: Env id: env @@ -175,17 +183,17 @@ jobs: SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_RELEASE=${{ steps.env.outputs.EXPO_PUBLIC_RELEASE_VERSION }} SENTRY_DIST=${{ steps.env.outputs.EXPO_PUBLIC_BUNDLE_IDENTIFIER }} - yarn export + pnpm export - name: 📦 Package Bundle and 🚀 Deploy - run: yarn use-build-number bash scripts/bundleUpdate.sh + run: pnpm use-build-number bash scripts/bundleUpdate.sh env: DENIS_API_KEY: ${{ secrets.DENIS_API_KEY }} CHANNEL_NAME: pull-request-${{ github.event.issue.number }} RUNTIME_VERSION: - name: 💬 Drop a comment - uses: marocchino/sticky-pull-request-comment@v2 + uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 env: ISSUE_NUMBER: ${{ github.event.issue.number }} with: @@ -202,7 +210,7 @@ jobs: *Generated by [PR labeler](https://github.com/expo/expo/actions/workflows/pr-labeler.yml) 🤖* - name: 💬 Drop a comment - uses: marocchino/sticky-pull-request-comment@v2 + uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 if: failure() with: header: pull-request-eas-build-${{ steps.pr-info.outputs.head-sha }} diff --git a/.github/workflows/pull-request-commit.yml b/.github/workflows/pull-request-commit.yml index cc2dbe78bd..df8a69c188 100644 --- a/.github/workflows/pull-request-commit.yml +++ b/.github/workflows/pull-request-commit.yml @@ -13,26 +13,31 @@ concurrency: group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }} cancel-in-progress: true -permissions: - pull-requests: write - actions: write - contents: read +# Permissions are granted per-job below; anything unlisted defaults to none. +# pull-requests: write is needed by sticky-pull-request-comment to post the +# bundle-size and fingerprint diffs +permissions: {} jobs: webpack-analyzer: runs-on: ubuntu-22.04 if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event_name == 'pull_request'}} + permissions: + contents: read + pull-requests: write steps: - name: ⬇️ Checkout - uses: actions/checkout@v4 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: fetch-depth: 0 + - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + - name: 🔧 Setup Node - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version-file: .nvmrc - cache: yarn + node-version-file: package.json + cache: pnpm - name: Ensure tracking relevant branches and checkout base env: @@ -56,17 +61,17 @@ jobs: git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" git merge --no-edit $HEAD_REF - yarn install - yarn intl:build + pnpm install + pnpm intl:build - name: 🔦 Generate stats file for PR run: | - yarn generate-webpack-stats-file + pnpm generate-webpack-stats-file mv stats.json ../stats-new.json - name: ⬇️ Get base stats from cache id: get-base-stats - uses: actions/cache@v4 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: stats-base.json key: stats-base-${{ steps.base-commit.outputs.base-commit }} @@ -80,21 +85,21 @@ jobs: - name: 🔦 Generate stats file from base commit if: ${{ !steps.get-base-stats.outputs.cache-hit }} run: | - yarn install - yarn intl:build - yarn generate-webpack-stats-file + pnpm install + pnpm intl:build + pnpm generate-webpack-stats-file mv stats.json stats-base.json - name: % Get diff id: get-diff - uses: NejcZdovc/bundle-size-diff@v1 + uses: NejcZdovc/bundle-size-diff@5321de41d2d62a7b0f4d6e60f59d1280a0034160 # v1.1.0 with: base_path: "stats-base.json" pr_path: "../stats-new.json" excluded_assets: "(.+).chunk.js|(.+).js.map|(.+).json|(.+).png|(.+).svg|(.+).webp|(.+).jpg|(.+).ico" - name: 💬 Drop a comment - uses: marocchino/sticky-pull-request-comment@v2 + uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 with: header: bundle-diff message: | @@ -106,9 +111,12 @@ jobs: fingerprint-native: runs-on: ubuntu-22.04 if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event_name == 'pull_request'}} + permissions: + contents: read + pull-requests: write steps: - name: ⬇️ Checkout - uses: actions/checkout@v4 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: fetch-depth: 100 @@ -116,20 +124,22 @@ jobs: run: git fetch origin main:main --depth 100 if: github.event_name == 'pull_request' + - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + - name: 🔧 Setup Node - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version-file: .nvmrc - cache: yarn + node-version-file: package.json + cache: pnpm - name: 📷 Check fingerprint and install dependencies id: fingerprint - uses: bluesky-social/github-actions/fingerprint-native@main + uses: bluesky-social/github-actions/fingerprint-native@ebc6aa6d7466dc1e78b1e832041b7b81f6f95030 # v0.1.0 with: profile: pull-request - name: 💬 Drop a comment - uses: marocchino/sticky-pull-request-comment@v2 + uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 if: ${{ steps.fingerprint.outputs.includes-changes }} with: header: fingerprint-diff @@ -147,7 +157,7 @@ jobs: *Generated by [PR labeler](https://github.com/expo/expo/actions/workflows/pr-labeler.yml) 🤖* - name: 💬 Delete comment - uses: marocchino/sticky-pull-request-comment@v2 + uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 if: ${{ !steps.fingerprint.outputs.includes-changes }} with: header: fingerprint-diff diff --git a/.github/workflows/sync-internal.yaml b/.github/workflows/sync-internal.yaml index b50d6bc512..3b07ae2b90 100644 --- a/.github/workflows/sync-internal.yaml +++ b/.github/workflows/sync-internal.yaml @@ -4,22 +4,29 @@ on: push: branches: [main] +# The push to the internal repo uses the app token below, not the GITHUB_TOKEN +permissions: + contents: read + jobs: sync: runs-on: ubuntu-latest if: github.repository == 'bluesky-social/social-app' steps: - name: Checkout public repo - uses: actions/checkout@v4 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: fetch-depth: 0 - name: Generate GitHub App Token id: app-token - uses: actions/create-github-app-token@v1 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: app-id: ${{ vars.SYNC_INTERNAL_APP_ID }} private-key: ${{ secrets.SYNC_INTERNAL_PK }} repositories: social-app-internal + # Scope the token down from the app's full installation permissions; + # pushing is the only thing this token is used for + permission-contents: write - name: Push to internal repo env: TOKEN: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/verify-pnpm-lock.yml b/.github/workflows/verify-pnpm-lock.yml new file mode 100644 index 0000000000..cb68e2b4e7 --- /dev/null +++ b/.github/workflows/verify-pnpm-lock.yml @@ -0,0 +1,51 @@ +name: Lockfile + +on: + pull_request: +concurrency: + group: "${{ github.workflow }}-${{ github.head_ref || github.ref }}" + cancel-in-progress: true + +permissions: + contents: read + +jobs: + verify-pnpm-lock: + name: No manual pnpm-lock.yaml edits + runs-on: ubuntu-latest + steps: + - name: Check out PR HEAD + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + fetch-depth: 0 + + - name: Fetch base branch + env: + BASE_REF: ${{ github.base_ref }} + run: git fetch origin $BASE_REF --depth=1 + + - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + + - name: Install node + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version-file: package.json + + - name: Reset pnpm-lock.yaml to base + env: + BASE_REF: ${{ github.base_ref }} + run: git show "origin/$BASE_REF:pnpm-lock.yaml" > pnpm-lock.yaml + + - name: pnpm install + # Fine to skip scripts since we don't run any code + run: pnpm clean && pnpm install --ignore-scripts --no-frozen-lockfile + + - name: Verify pnpm-lock.yaml + run: | + git diff --quiet --exit-code || { + echo '::error::`pnpm-lock.yaml` does not match what pnpm would generate given the base `pnpm-lock.yaml` and the head `package.json`.' + echo '::error:: - If this is intentional, you can ignore this check.' + echo '::error:: - If this is unintentional, apply the following diff:' + git --no-pager diff + exit 1 + } diff --git a/.github/workflows/verify-yarn-lock.yml b/.github/workflows/verify-yarn-lock.yml deleted file mode 100644 index 6eadf7be76..0000000000 --- a/.github/workflows/verify-yarn-lock.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Lockfile - -on: - pull_request: -concurrency: - group: "${{ github.workflow }}-${{ github.head_ref || github.ref }}" - cancel-in-progress: true - -jobs: - verify-yarn-lock: - name: No manual yarn.lock edits - runs-on: ubuntu-latest - steps: - - name: Check out PR HEAD - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Fetch base branch - env: - BASE_REF: ${{ github.base_ref }} - run: git fetch origin $BASE_REF --depth=1 - - - name: Install node - uses: actions/setup-node@v6 - with: - node-version-file: .nvmrc - - - name: Reset yarn.lock to base - env: - BASE_REF: ${{ github.base_ref }} - run: git show "origin/$BASE_REF:yarn.lock" > yarn.lock - - - name: Yarn install - uses: Wandalen/wretry.action@master - with: - # Fine to skip scripts since we don't run any code - command: yarn install --ignore-scripts - attempt_limit: 3 - attempt_delay: 2000 - - - name: Verify yarn.lock - run: | - git diff --quiet --exit-code || { - echo '::error::`yarn.lock` does not match what Yarn would generate given the base `yarn.lock` and the head `package.json`.' - echo '::error:: - If this is intentional, you can ignore this check.' - echo '::error:: - If this is unintentional, apply the following diff:' - git --no-pager diff - exit 1 - } diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml new file mode 100644 index 0000000000..56d06bbd85 --- /dev/null +++ b/.github/workflows/zizmor.yml @@ -0,0 +1,37 @@ +name: Workflow security + +on: + push: + branches: [main] + paths: [".github/**"] + pull_request: + paths: [".github/**"] + +concurrency: + group: "${{ github.workflow }}-${{ github.head_ref || github.ref }}" + cancel-in-progress: true + +# The github.token is only used by zizmor's online audits (read-only API calls) +permissions: + contents: read + +jobs: + zizmor: + name: Audit workflows with zizmor + runs-on: ubuntu-latest + steps: + - name: Check out Git repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + + - name: Run zizmor + uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6 + with: + # Annotate the PR directly instead of uploading SARIF to the + # security tab, and fail the check on any finding + advanced-security: false + annotations: true + # Low-confidence findings (e.g. artipacked on workflows that never + # push) are too noisy to gate CI on + min-confidence: medium diff --git a/.gitignore b/.gitignore index c0adc37228..015e13d3de 100644 --- a/.gitignore +++ b/.gitignore @@ -86,6 +86,9 @@ stats.json # VSCode .vscode +# Zed +.zed + # gitignore and github actions !.gitignore !.github @@ -127,3 +130,7 @@ bskyweb/static/media/*.webp bskyweb/static/media/*.jpg bskyweb/static/media/*.png bskyweb/static/media/*.svg + +# superpowers plugin plans/specs — local-only workspace +docs/superpowers/ +.claude/worktrees diff --git a/.husky/pre-commit b/.husky/pre-commit index c8fec63e2a..8ba25f36cd 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1 @@ -#!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" - -npx lint-staged --concurrent false +pnpm exec lint-staged --concurrent false diff --git a/.jscodeshift/file/lingui-v5.js b/.jscodeshift/file/lingui-v5.js new file mode 100644 index 0000000000..81daf6f018 --- /dev/null +++ b/.jscodeshift/file/lingui-v5.js @@ -0,0 +1,236 @@ +/** + * Codemod to update Lingui's useLingui() hook to use v5. + * + * Before: + * import {msg} from '@lingui/core/macro' + * import {useLingui} from '@lingui/react' + * const {_} = useLingui() + * _(msg`Lorem ipsum`) + * + * After: + * import {useLingui} from '@lingui/react/macro' + * const {t: l} = useLingui() + * l`Lorem ipsum` + * + * Usage: jscodeshift -t .jscodeshift/file/lingui-v5.js + * Example: jscodeshift -t .jscodeshift/file/lingui-v5.js modules/bottom-sheet/src/lib/Portal.tsx + */ + +/* eslint-disable */ + +export const parser = 'tsx' + +export default function transformer(file, api) { + const j = api.jscodeshift + + const root = j(file.source) + + // Remove msg imports, including aliased imports + root + .find(j.ImportDeclaration) + .filter(path => path.node.source.value === '@lingui/core/macro') + .forEach(path => { + const newSpecifiers = path.node.specifiers.filter(specifier => { + if (j.ImportSpecifier.check(specifier)) { + const importedName = specifier.imported + ? specifier.imported.name + : specifier.local.name + const localName = specifier.local + ? specifier.local.name + : importedName + + return !( + importedName === 'msg' && + (localName === 'msg' || localName === 'msgLingui') + ) + } + return true + }) + + if (newSpecifiers.length > 0) { + path.node.specifiers = newSpecifiers + } else { + j(path).remove() + } + }) + + // Update React imports + root + .find(j.ImportDeclaration) + .filter(path => path.node.source.value === '@lingui/react') + .forEach(path => { + path.node.source = j.stringLiteral('@lingui/react/macro') + }) + + // Replace {_} with {t: l} + root + .find(j.VariableDeclarator, { + init: { + type: 'CallExpression', + callee: {name: 'useLingui'}, + }, + }) + .replaceWith(nodePath => { + const {node} = nodePath + for (let i = 0; i < node.id.properties.length; i++) { + if (node.id.properties[i].key.name === '_') { + node.id.properties[i].key = j.identifier('t') + node.id.properties[i].shorthand = false + node.id.properties[i].value = j.identifier('l') + } + } + return node + }) + + // Replace {_: _l} with {t: l} + root + .find(j.VariableDeclarator, { + init: {callee: {name: 'useLingui'}}, + id: { + properties: properties => + properties.some(p => p.key.name === '_' && p.value.name === '_l'), + }, + }) + .forEach(path => { + const prop = path.node.id.properties.find( + p => p.key.name === '_' && p.value.name === '_l', + ) + if (prop) { + prop.key.name = 't' + prop.value.name = 'l' + } + }) + + // Replace _(msg``) with l`` + root + .find(j.CallExpression, { + callee: {name: '_'}, + arguments: [ + { + type: 'TaggedTemplateExpression', + tag: {name: 'msg'}, + }, + ], + }) + .replaceWith(path => { + const quasis = path.node.arguments[0].quasi + + return j.taggedTemplateExpression(j.identifier('l'), quasis) + }) + + // Replace _l(msgLingui``) with l`` + root + .find(j.CallExpression, { + callee: {name: '_l'}, + arguments: [ + { + type: 'TaggedTemplateExpression', + tag: {name: 'msgLingui'}, + }, + ], + }) + .replaceWith(path => { + const quasis = path.node.arguments[0].quasi + + return j.taggedTemplateExpression(j.identifier('l'), quasis) + }) + + // Replace _(msg()) with l() + root + .find(j.CallExpression, { + callee: {name: '_'}, + arguments: [ + { + type: 'CallExpression', + callee: {name: 'msg'}, + }, + ], + }) + .replaceWith(path => { + return j.callExpression(j.identifier('l'), [ + path.node.arguments[0].arguments[0], + ]) + }) + + // Replace _l(msgLingui()) with l() + root + .find(j.CallExpression, { + callee: {name: '_l'}, + arguments: [ + { + type: 'CallExpression', + callee: {name: 'msgLingui'}, + }, + ], + }) + .replaceWith(path => { + return j.callExpression(j.identifier('l'), [ + path.node.arguments[0].arguments[0], + ]) + }) + + // Replace _(plural()) with plural() + root + .find(j.CallExpression, { + callee: { + type: 'Identifier', + name: '_', + }, + }) + .forEach(path => { + const callExpression = path.node + if ( + callExpression.arguments.length === 1 && + callExpression.arguments[0].type === 'CallExpression' + ) { + const innerCall = callExpression.arguments[0] + + if ( + innerCall.callee.type === 'Identifier' && + innerCall.callee.name === 'plural' + ) { + j(path).replaceWith(innerCall) + } + } + }) + + // Update hook dependency arrays + root + .find(j.CallExpression, { + callee: callee => { + if ( + callee.type === 'Identifier' || + callee.type === 'MemberExpression' + ) { + return true + } + if ( + ['useCallback', 'useEffect', 'useMemo'].includes(callee.name) && + callee.type === 'MemberExpression' && + callee.object.type === 'Identifier' && + callee.object.name === 'React' && + callee.property.type === 'Identifier' && + callee.property.name === 'useCallback' + ) { + return true + } + return false + }, + }) + .forEach(path => { + const dependencyArray = path.node.arguments[1] + if (dependencyArray && dependencyArray.type === 'ArrayExpression') { + dependencyArray.elements = dependencyArray.elements.map(element => { + if ( + element.type === 'Identifier' && + (element.name === '_' || element.name === '_l') + ) { + return j.identifier('l') + } + return element + }) + } + }) + + return root.toSource() +} diff --git a/.jscodeshift/repo/bsky-agent.js b/.jscodeshift/repo/bsky-agent.js new file mode 100644 index 0000000000..57e501959d --- /dev/null +++ b/.jscodeshift/repo/bsky-agent.js @@ -0,0 +1,62 @@ +/** + * Codemod to replace BskyAgent with AtpAgent + * + * Before: + * import {BskyAgent} from '@atproto/api` + * BskyAgent.appLabelers.includes(labeler) + * + * After: + * import {AtpAgent} from '@atproto/api` + * AtpAgent.appLabelers.includes(labeler) + * + * Handles import specifiers, type annotations, static member access + * (BskyAgent.configure), `extends BskyAgent`, and `new BskyAgent()`. Whole + * identifiers only, so names like `OpaqueBskyAgent` are left untouched. + * + * Usage: jscodeshift -t .jscodeshift/repo/bsky-agent.js + * Example: jscodeshift -t .jscodeshift/repo/bsky-agent.js src/lib/moderation.ts + */ + +/* eslint-disable */ + +export const parser = 'tsx' + +export default function transformer(file, api) { + const j = api.jscodeshift + const root = j(file.source) + + // Replace every standalone `BskyAgent` identifier with `AtpAgent`. This + // covers imports, type references, member expressions, `extends`, and `new`. + root + .find(j.Identifier, {name: 'BskyAgent'}) + .replaceWith(() => j.identifier('AtpAgent')) + + // Renaming can leave a duplicate `AtpAgent` specifier on the @atproto/api + // import if the file already imported it. Dedupe by imported name, keeping + // the type-only modifier only if every duplicate was type-only. + root + .find(j.ImportDeclaration, {source: {value: '@atproto/api'}}) + .forEach(path => { + const seen = new Map() + for (const spec of path.value.specifiers) { + if (spec.type !== 'ImportSpecifier') { + seen.set(Symbol(), spec) + continue + } + const name = spec.imported.name + const existing = seen.get(name) + if (!existing) { + seen.set(name, spec) + } else if ( + existing.importKind === 'type' && + spec.importKind !== 'type' + ) { + // Prefer the value (non-type) import if either usage needs it. + seen.set(name, spec) + } + } + path.value.specifiers = Array.from(seen.values()) + }) + + return root.toSource() +} diff --git a/.jscodeshift/react-import.js b/.jscodeshift/repo/react-import.js similarity index 95% rename from .jscodeshift/react-import.js rename to .jscodeshift/repo/react-import.js index e63238f862..a6a7329dd5 100644 --- a/.jscodeshift/react-import.js +++ b/.jscodeshift/repo/react-import.js @@ -9,8 +9,8 @@ * import { useEffect } from 'react' * useEffect(() => {}, []) * - * Usage: jscodeshift -t .jscodeshift/react-import.js - * Example: jscodeshift -t .jscodeshift/react-import.js src/App.native.tsx + * Usage: jscodeshift -t .jscodeshift/repo/react-import.js + * Example: jscodeshift -t .jscodeshift/repo/react-import.js src/App.native.tsx */ /* eslint-disable */ diff --git a/.jscodeshift/toast-v2.js b/.jscodeshift/repo/toast-v2.js similarity index 94% rename from .jscodeshift/toast-v2.js rename to .jscodeshift/repo/toast-v2.js index a60f2da4ab..d2b51d4901 100644 --- a/.jscodeshift/toast-v2.js +++ b/.jscodeshift/repo/toast-v2.js @@ -9,8 +9,8 @@ * import * as Toast from '#/components/Toast' * Toast.show(message, {type: 'error'}) * - * Usage: jscodeshift -t .jscodeshift/toast-v2.js - * Example: jscodeshift -t .jscodeshift/toast-v2.js src/App.native.tsx + * Usage: jscodeshift -t .jscodeshift/repo/toast-v2.js + * Example: jscodeshift -t .jscodeshift/repo/toast-v2.js src/App.native.tsx */ /* eslint-disable */ diff --git a/.nvmrc b/.nvmrc index 209e3ef4b6..5bf4400f22 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -20 +24.15.0 diff --git a/.prettierignore b/.prettierignore index e107e129a4..afc71dbac1 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,10 +5,14 @@ !**/*.jsx !**/*.ts !**/*.tsx +!**/*.mjs +!**/*.cjs !*/ !**/*.css +!**/pnpm-workspace.yaml + # More specific ignores go below. .expo android diff --git a/CLAUDE.md b/CLAUDE.md index a15b5bad88..37e6a1c2c5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,4 +1,4 @@ -# CLAUDE.md - Bluesky Social App Development Guide +# CLAUDE.md – Bluesky Social App Development Guide This document provides guidance for working effectively in the Bluesky Social app codebase. @@ -7,36 +7,40 @@ This document provides guidance for working effectively in the Bluesky Social ap Bluesky Social is a cross-platform social media application built with React Native and Expo. It runs on iOS, Android, and Web, connecting to the AT Protocol (atproto) decentralized social network. **Tech Stack:** + +- React 19.1 - React Native 0.81 with Expo 54 -- TypeScript -- React Navigation for routing +- TypeScript 6 +- React Navigation 7 for routing - TanStack Query (React Query) for data fetching -- Lingui for internationalization +- Lingui 5 for internationalization - Custom design system called ALF (Application Layout Framework) +Prefer using the latest features available for each of these libraries (exact versions are found in `package.json`). For example, prefer `@lingui/react/macro` over `@lingui/react`. Suggest refactoring legacy or deprecated uses. + ## Essential Commands ```bash # Development -yarn start # Start Expo dev server -yarn web # Start web version -yarn android # Run on Android -yarn ios # Run on iOS +pnpm start # Start Expo dev server +pnpm web # Start web version +pnpm android # Run on Android +pnpm ios # Run on iOS # Testing & Quality -# IMPORTANT: Always use these yarn scripts, never call the underlying tools directly -yarn test # Run Jest tests -yarn lint # Run ESLint -yarn typecheck # Run TypeScript type checking +# IMPORTANT: Always use these pnpm scripts, never call the underlying tools directly +pnpm test # Run Jest tests +pnpm lint # Run ESLint +pnpm typecheck # Run TypeScript type checking # Internationalization # DO NOT run these commands - extraction and compilation are handled by CI -yarn intl:extract # Extract translation strings (nightly CI job) -yarn intl:compile # Compile translations for runtime (nightly CI job) +pnpm intl:extract # Extract translation strings (nightly CI job) +pnpm intl:compile # Compile translations for runtime (nightly CI job) # Build -yarn build-web # Build web version -yarn prebuild # Generate native projects +pnpm build-web # Build web version +pnpm prebuild # Generate native projects ``` ## Project Structure @@ -162,6 +166,10 @@ its own looks more like a single component file. ### Documentation and Tests Within Features +Comment code when necessary to explain the “why” behind something; avoid +comments that simply describe the code. Avoid Unicode characters in comments, +e.g., use `-` not `—`. + For larger features or components, it's helpful to include a README.md file within the directory that explains the purpose of the feature, how it works, and any important implementation details. The `/Component/index.tsx` pattern lends @@ -182,6 +190,14 @@ ALF is the custom design system. It uses Tailwind-inspired naming with underscor ### Basic Usage +Generally, order atoms by: + +- Flexbox configuration, e.g., `a.flex_row` +- Spacing, e.g., `a.px_md` +- Text styles, e.g., `a.font_bold` +- Themes, e.g., `t.atoms.text`, +- Raw styles, e.g., `{backgroundColor: t.palette.primary_500}` + ```tsx import {atoms as a, useTheme} from '#/alf' @@ -190,9 +206,7 @@ function MyComponent() { return ( - - Hello - + Hello ) } @@ -200,20 +214,23 @@ function MyComponent() { ### Key Concepts -**Static Atoms** - Theme-independent styles imported from `atoms`: +**Static Atoms** – Theme-independent styles imported from `atoms`: + ```tsx import {atoms as a} from '#/alf' // a.flex_row, a.p_md, a.gap_sm, a.rounded_md, a.text_lg, etc. ``` -**Theme Atoms** - Theme-dependent colors from `useTheme()`: +**Theme Atoms** – Theme-dependent colors from `useTheme()`: + ```tsx const t = useTheme() // t.atoms.bg, t.atoms.text, t.atoms.border_contrast_low, etc. // t.palette.primary_500, t.palette.negative_400, etc. ``` -**Platform Utilities** - For platform-specific styles: +**Platform Utilities** – For platform-specific styles: + ```tsx import {web, native, ios, android, platform} from '#/alf' @@ -225,7 +242,8 @@ const styles = [ ] ``` -**Breakpoints** - Responsive design: +**Breakpoints** – Responsive design: + ```tsx import {useBreakpoints} from '#/alf' @@ -245,6 +263,38 @@ if (gtMobile) { ## Component Patterns +- Prefer fragment shorthand over `Fragment` unless a `key` is needed. +- Prefer functions over arrow functions for component declarations. +- Prefer prop destructuring via parameters over a const within the component. +- Prefer inline types over `Props` types or interfaces. +- Set reasonable defaults for optional props. + +```tsx +import {Fragment} from 'react' +import {View} from 'react-native' +import {Trans, useLingui} from '@lingui/react/macro' + +import {Text} from '#/components/Typography' + +function MyComponent({foo = []}: {foo?: string[]}) { + const {t: l} = useLingui() + + return ( + <> + Example + + {foo.map((foo, index) => ( + + {index} + {foo} + + ))} + + + ); +} +``` + ### Dialog Component Dialogs use a bottom sheet on native and a modal on web. Use `useDialogControl()` hook to manage state. @@ -263,23 +313,29 @@ function MyFeature() { {/* Typically the inner part is in its own component */} - {/* Native-only drag handle */} - - - Title - - - Dialog content here - - - {/* Web-only X button in top left */} - + ) } + +function DialogInner() { + return ( + <> + {/* Native-only drag handle */} + + + Title + + Dialog content here + + {/* Web-only X button in top left */} + + + ) +} ``` ### Menu Component @@ -345,6 +401,7 @@ import {Button, ButtonText, ButtonIcon} from '#/components/Button' ``` **Button Props:** + - `color`: `'primary'` | `'secondary'` | `'negative'` | `'primary_subtle'` | `'negative_subtle'` | `'secondary_inverted'` - `size`: `'tiny'` | `'small'` | `'large'` - `shape`: `'default'` (pill) | `'round'` | `'square'` | `'rectangular'` @@ -357,12 +414,16 @@ import {Text, H1, H2, P} from '#/components/Typography'

Heading

Paragraph text with default styling.

-Custom text +Custom text -// For text with emoji, add the emoji prop +// For text with emoji, add the emoji prop. User-generated text (e.g. display names) +// will almost certainly contain emoji, so only omit it when the text is static and +// does not contain an emoji Hello! 👋 ``` +The `Text` component's default style is `[a.text_sm, a.leading_snug, t.atoms.text]`. + ### TextField ```tsx @@ -384,43 +445,70 @@ import * as TextField from '#/components/forms/TextField' ## Internationalization (i18n) -All user-facing strings must be wrapped for translation using Lingui. +All user-facing strings must be wrapped for translation using Lingui. Include `comment` and/or `context` props when necessary to avoid ambiguity, e.g., “Post” as a noun vs a verb. + +Prefer using `t` via `import {useLingui} '@lingui/react/macro'` vs `_` via `import {useLingui} from '@lingui/react'`. Alias `t` to `l` to avoid collisions with `const t = useTheme()`. Refactor existing uses of ``_(msg`foo`)`` to use `` l`foo` ``. + +Prefer Unicode punctuation over keyboard punctuation, e.g., `“quote”` over `"quote"`. Prefer en dashes preceded by a non-breaking space over em dashes, e.g., `one – two` over `one—two`. ```tsx -import {msg, plural} from '@lingui/core/macro' -import {Trans} from '@lingui/react/macro' -import {useLingui} from '@lingui/react' +import {plural} from '@lingui/core/macro' +import {Trans, useLingui} from '@lingui/react/macro' function MyComponent() { - const {_} = useLingui() + const {t: l} = useLingui() - // Simple strings - use msg() with _() function - const title = _(msg`Settings`) - const errorMessage = _(msg`Something went wrong`) + // Simple strings - use the l macro + const title = l`Settings` + const errorMessage = l({ + message: 'Something went wrong', + comment: 'Generic error message for unknown/unhandled errors.', + context: 'Toast', + }) // Strings with variables - const greeting = _(msg`Hello, ${name}!`) + const greeting = l`Hello, ${name}!` // Pluralization - const countLabel = _(plural(count, { + const countLabel = plural(count, { one: '# item', other: '# items', - })) + }) // JSX content - use Trans component return ( - Welcome to Bluesky + + Welcome to Bluesky, {name}! + ) } ``` +Prefer `i18n.date` for date and time formatting. This ensures formatting is re-applied when the language changes at runtime. Refactor existing uses of `Intl.DateTimeFormat` to use `i18n.date`. + +```tsx +import {useLingui} from '@lingui/react/macro' + +function MyComponent() { + const {i18n} = useLingui() + + const createdAt = new Date() + + return i18n.date(createdAt, { + dateStyle: 'medium', + timeStyle: 'medium', + }) +} +``` + **Commands:** + ```bash # DO NOT run these commands - extraction and compilation are handled by a nightly CI job -yarn intl:extract # Extract new strings to locale files -yarn intl:compile # Compile translations for runtime +pnpm intl:extract # Extract new strings to locale files +pnpm intl:compile # Compile translations for runtime ``` ## State Management @@ -473,7 +561,7 @@ export function useProfileMutation() { const queryClient = useQueryClient() return useMutation({ - mutationFn: async (data) => { + mutationFn: async data => { // Update logic }, onSuccess: (_, variables) => { @@ -481,7 +569,7 @@ export function useProfileMutation() { queryKey: createProfileQueryKey({did: variables.did}), }) }, - onError: (error) => { + onError: error => { if (isNetworkError(error)) { // don't log, but inform user } else if (error instanceof AppBskyExampleProcedure.ExampleError) { @@ -490,7 +578,7 @@ export function useProfileMutation() { // Log unexpected errors to Sentry logger.error('Error updating profile', {safeMessage: error}) } - } + }, }) } @@ -505,21 +593,25 @@ export function useProfileCacheMutation() { const queryClient = useQueryClient() return (data: Partial) => { - queryClient.setQueryData(createProfileQueryKey({did: data.did}), oldData => { - if (!oldData) return oldData - return {...oldData, ...data} - }) + queryClient.setQueryData( + createProfileQueryKey({did: data.did}), + oldData => { + if (!oldData) return oldData + return {...oldData, ...data} + }, + ) } } ``` **Stale Time Constants** (from `src/state/queries/index.ts`): + ```tsx -STALE.SECONDS.FIFTEEN // 15 seconds -STALE.MINUTES.ONE // 1 minute -STALE.MINUTES.FIVE // 5 minutes -STALE.HOURS.ONE // 1 hour -STALE.INFINITY // Never stale +STALE.SECONDS.FIFTEEN // 15 seconds +STALE.MINUTES.ONE // 1 minute +STALE.MINUTES.FIVE // 5 minutes +STALE.HOURS.ONE // 1 hour +STALE.INFINITY // Never stale ``` **Paginated APIs:** Many atproto APIs return paginated results with a `cursor`. Use `useInfiniteQuery` for these: @@ -565,12 +657,7 @@ function SettingsScreen() { const autoplayDisabled = useAutoplayDisabled() const setAutoplayDisabled = useSetAutoplayDisabled() - return ( - - ) + return } ``` @@ -604,13 +691,9 @@ import {type CommonNavigatorParams} from '#/lib/routes/types' type Props = NativeStackScreenProps export function ProfileScreen({route, navigation}: Props) { - const {name} = route.params // Type-safe params + const {name} = route.params // Type-safe params - return ( - - {/* Screen content */} - - ) + return {/* Screen content */} } // Programmatic navigation @@ -637,8 +720,9 @@ Component.android.tsx # Android-only ``` Example from Dialog: -- `src/components/Dialog/index.tsx` - Native (uses BottomSheet) -- `src/components/Dialog/index.web.tsx` - Web (uses modal with Radix primitives) + +- `src/components/Dialog/index.tsx` – Native (uses BottomSheet) +- `src/components/Dialog/index.web.tsx` – Web (uses modal with Radix primitives) **Important:** The bundler automatically resolves platform-specific files. Just import normally: @@ -653,6 +737,7 @@ const storage = IS_NATIVE ``` Platform detection (for runtime logic, not imports): + ```tsx import {IS_WEB, IS_NATIVE, IS_IOS, IS_ANDROID} from '#/env' @@ -687,13 +772,13 @@ Common pitfalls to avoid in this codebase: // WRONG - causes bugs with state updates, navigation, opening other dialogs const onConfirm = () => { control.close() - navigation.navigate('Home') // May race with dialog animation + navigation.navigate('Home') // May race with dialog animation } // WRONG - same problem const onConfirm = () => { control.close() - otherDialogControl.open() // Will likely fail or cause visual glitches + otherDialogControl.open() // Will likely fail or cause visual glitches } // CORRECT - action runs after dialog fully closes @@ -720,12 +805,13 @@ const onConfirm = () => { ``` This applies to: + - Navigation (`navigation.navigate()`, `navigation.push()`) - Opening other dialogs or menus - State updates that affect UI (`setState`, `queryClient.invalidateQueries`) - Callbacks passed from parent components -The Menu component on iOS specifically uses this pattern - see `src/components/Menu/index.tsx:151`. +The Menu component on iOS specifically uses this pattern – see `src/components/Menu/index.tsx:151`. ### Controlled vs Uncontrolled Inputs @@ -748,10 +834,11 @@ Prefer `defaultValue` over `value` for TextInput on the old architecture: ### Platform-Specific Behavior Some components behave differently across platforms: -- `Dialog.Handle` - Only renders on native (drag handle for bottom sheet) -- `Dialog.Close` - Only renders on web (X button) -- `Menu.Divider` - Only renders on web -- `Menu.ContainerItem` - Only works on native + +- `Dialog.Handle` – Only renders on native (drag handle for bottom sheet) +- `Dialog.Close` – Only renders on web (X button) +- `Menu.Divider` – Only renders on web +- `Menu.ContainerItem` – Only works on native Always test on multiple platforms when using these components. @@ -772,6 +859,7 @@ const handlePress = () => { ``` Only use `useMemo`/`useCallback` when you have a specific reason, such as: + - The value is immediately used in an effect's dependency array - You're passing a callback to a non-React library that needs referential stability @@ -779,7 +867,7 @@ Only use `useMemo`/`useCallback` when you have a specific reason, such as: 1. **Accessibility**: Always provide `label` prop for interactive elements, use `accessibilityHint` where helpful -2. **Translations**: Wrap ALL user-facing strings with `msg()` or `` +2. **Translations**: Wrap ALL user-facing strings with ` `l` `` or `` 3. **Styling**: Combine static atoms with theme atoms, use platform utilities for platform-specific styles @@ -793,14 +881,14 @@ Only use `useMemo`/`useCallback` when you have a specific reason, such as: ## Key Files Reference -| Purpose | Location | -|---------|----------| -| Theme definitions | `src/alf/themes.ts` | -| Design tokens | `src/alf/tokens.ts` | -| Static atoms | `src/alf/atoms.ts` (extends `@bsky.app/alf`) | -| Navigation config | `src/Navigation.tsx` | -| Route definitions | `src/routes.ts` | -| Route types | `src/lib/routes/types.ts` | -| Query hooks | `src/state/queries/*.ts` | -| Session state | `src/state/session/index.tsx` | -| i18n setup | `src/locale/i18n.ts` | +| Purpose | Location | +| ----------------- | -------------------------------------------- | +| Theme definitions | `src/alf/themes.ts` | +| Design tokens | `src/alf/tokens.ts` | +| Static atoms | `src/alf/atoms.ts` (extends `@bsky.app/alf`) | +| Navigation config | `src/Navigation.tsx` | +| Route definitions | `src/routes.ts` | +| Route types | `src/lib/routes/types.ts` | +| Query hooks | `src/state/queries/*.ts` | +| Session state | `src/state/session/index.tsx` | +| i18n setup | `src/locale/i18n.ts` | diff --git a/Dockerfile b/Dockerfile index 32a45d2ca9..fff191cc58 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,23 +1,21 @@ -FROM golang:1.26-bookworm AS build-env +# +# Stage 1: build the web bundle with pnpm. +# +# Uses the official pnpm image. Node is auto-downloaded by pnpm using the +# `devEngines.runtime` field in package.json (onFail: "download"). +# +FROM ghcr.io/pnpm/pnpm:11 AS web-build -WORKDIR /usr/src/social-app +WORKDIR /app ENV DEBIAN_FRONTEND=noninteractive # -# Node +# pnpm config # -ENV NODE_VERSION=20 -ENV NVM_DIR=/usr/share/nvm - -# -# Go -# -ENV GODEBUG="netdns=go" -ENV GOOS="linux" -ENV GOARCH="amd64" -ENV CGO_ENABLED=1 -ENV GOEXPERIMENT="loopvar" +ENV CI=1 +# use the pnpm version specified in package.json +ENV pnpm_config_pm_on_fail=download # The latest git hash of the preview branch on render.com # https://render.com/docs/docker-secrets#environment-variables-in-docker-builds @@ -42,41 +40,48 @@ ENV SENTRY_AUTH_TOKEN=${SENTRY_AUTH_TOKEN:-unknown} ARG EXPO_PUBLIC_SENTRY_DSN ENV EXPO_PUBLIC_SENTRY_DSN=$EXPO_PUBLIC_SENTRY_DSN -# -# Copy everything into the container -# COPY . . -# -# Generate the JavaScript webpack. -# -RUN mkdir --parents $NVM_DIR && \ - wget \ - --output-document=/tmp/nvm-install.sh \ - https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh && \ - bash /tmp/nvm-install.sh - -RUN \. "$NVM_DIR/nvm.sh" && \ - nvm install $NODE_VERSION && \ - nvm use $NODE_VERSION && \ - echo "Using bundle identifier: $EXPO_PUBLIC_BUNDLE_IDENTIFIER" && \ +RUN echo "Using bundle identifier: $EXPO_PUBLIC_BUNDLE_IDENTIFIER" && \ echo "EXPO_PUBLIC_ENV=$EXPO_PUBLIC_ENV" >> .env && \ echo "EXPO_PUBLIC_RELEASE_VERSION=$EXPO_PUBLIC_RELEASE_VERSION" >> .env && \ echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$EXPO_PUBLIC_BUNDLE_IDENTIFIER" >> .env && \ echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env && \ - echo "EXPO_PUBLIC_SENTRY_DSN=$EXPO_PUBLIC_SENTRY_DSN" >> .env && \ - npm install --global yarn && \ - yarn && \ - yarn intl:build 2>&1 | tee i18n.log && \ - if grep -q "invalid syntax" "i18n.log"; then echo "\n\nFound compilation errors!\n\n" && exit 1; else echo "\n\nNo compile errors!\n\n"; fi && \ - SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN SENTRY_RELEASE=$EXPO_PUBLIC_RELEASE_VERSION SENTRY_DIST=$EXPO_PUBLIC_BUNDLE_IDENTIFIER yarn build-web + echo "EXPO_PUBLIC_SENTRY_DSN=$EXPO_PUBLIC_SENTRY_DSN" >> .env + +RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile + +RUN pnpm intl:build 2>&1 | tee i18n.log && \ + if grep -q "invalid syntax" "i18n.log"; then echo "\n\nFound compilation errors!\n\n" && exit 1; else echo "\n\nNo compile errors!\n\n"; fi + +RUN SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN \ + SENTRY_RELEASE=$EXPO_PUBLIC_RELEASE_VERSION \ + SENTRY_DIST=$EXPO_PUBLIC_BUNDLE_IDENTIFIER \ + pnpm build-web + +# +# Stage 2: build the bskyweb Go binary, embedding the assets from stage 1. +# +# post-web-build.js (run by `pnpm build-web`) writes the bundled JS/CSS/media +# into bskyweb/static/* and regenerates bskyweb/templates/scripts.html, so +# copying the bskyweb/ tree from stage 1 is enough for go:embed to find +# everything. +# +FROM golang:1.26-bookworm AS go-build + +WORKDIR /usr/src/social-app + +ENV GODEBUG="netdns=go" +ENV GOOS="linux" +ENV GOARCH="amd64" +ENV CGO_ENABLED=1 +ENV GOEXPERIMENT="loopvar" + +COPY --from=web-build /app/bskyweb ./bskyweb # DEBUG -RUN find ./bskyweb/static && find ./web-build/static +RUN find ./bskyweb/static -# -# Generate the bskyweb Go binary. -# RUN cd bskyweb/ && \ go mod download && \ go mod verify @@ -89,6 +94,9 @@ RUN cd bskyweb/ && \ -o /bskyweb \ ./cmd/bskyweb +# +# Stage 3: runtime image. +# FROM debian:bookworm-slim ENV GODEBUG=netdns=go @@ -102,7 +110,7 @@ RUN apt-get update && apt-get install --yes \ ENTRYPOINT ["dumb-init", "--"] WORKDIR /bskyweb -COPY --from=build-env /bskyweb /usr/bin/bskyweb +COPY --from=go-build /bskyweb /usr/bin/bskyweb CMD ["/usr/bin/bskyweb"] diff --git a/Dockerfile.bskylink b/Dockerfile.bskylink index acde232256..934b40d1ad 100644 --- a/Dockerfile.bskylink +++ b/Dockerfile.bskylink @@ -1,4 +1,4 @@ -FROM node:20.11-alpine3.18 as build +FROM node:24.15.0-alpine3.22 AS build # Move files into the image and install WORKDIR /app @@ -14,7 +14,7 @@ RUN yarn build RUN yarn install --production --ignore-scripts --prefer-offline # Uses assets from build stage to reduce build size -FROM node:20.11-alpine3.18 +FROM node:24.15.0-alpine3.22 RUN apk add --update dumb-init @@ -29,8 +29,6 @@ VOLUME /app/data EXPOSE 3000 ENV LINK_PORT=3000 ENV NODE_ENV=production -# potential perf issues w/ io_uring on this version of node -ENV UV_USE_IO_URING=0 # https://github.com/nodejs/docker-node/blob/master/docs/BestPractices.md#non-root-user USER node diff --git a/Dockerfile.bskyogcard b/Dockerfile.bskyogcard index 10779a5661..dc9747f949 100644 --- a/Dockerfile.bskyogcard +++ b/Dockerfile.bskyogcard @@ -1,20 +1,25 @@ -FROM node:20.11-alpine3.18 as build +FROM node:24.15.0-alpine3.22 AS build + +# Tells pnpm to run non-interactively (needed for install/script steps) +ENV CI=true # Move files into the image and install WORKDIR /app COPY ./bskyogcard/package.json ./ -COPY ./bskyogcard/yarn.lock ./ -RUN yarn install --frozen-lockfile +COPY ./bskyogcard/pnpm-lock.yaml ./ +COPY ./bskyogcard/pnpm-workspace.yaml ./ +RUN npm install --global pnpm@11.5.2 +RUN pnpm install --frozen-lockfile COPY ./bskyogcard ./ # build then prune dev deps -RUN yarn install-fonts && yarn build -RUN yarn install --production --ignore-scripts --prefer-offline +RUN pnpm install-fonts && pnpm build +RUN pnpm install --prod --ignore-scripts --prefer-offline # Uses assets from build stage to reduce build size -FROM node:20.11-alpine3.18 +FROM node:24.15.0-alpine3.22 RUN apk add --update dumb-init @@ -30,8 +35,6 @@ EXPOSE 3000 3001 ENV CARD_PORT=3000 ENV CARD_METRICS_PORT=3001 ENV NODE_ENV=production -# potential perf issues w/ io_uring on this version of node -ENV UV_USE_IO_URING=0 # https://github.com/nodejs/docker-node/blob/master/docs/BestPractices.md#non-root-user USER node diff --git a/Dockerfile.embedr b/Dockerfile.embedr index 4f13b6d1a6..bf559ec583 100644 --- a/Dockerfile.embedr +++ b/Dockerfile.embedr @@ -5,16 +5,20 @@ WORKDIR /usr/src/social-app ENV DEBIAN_FRONTEND=noninteractive # Node -ENV NODE_VERSION=20 +ENV NODE_VERSION=24.15.0 ENV NVM_DIR=/usr/share/nvm # Go ENV GODEBUG="netdns=go" ENV GOOS="linux" -ENV GOARCH="amd64" +ARG TARGETARCH +ENV GOARCH=${TARGETARCH:-amd64} ENV CGO_ENABLED=1 ENV GOEXPERIMENT="loopvar" +# CI environment for pnpm +ENV CI=true + COPY . . # @@ -29,11 +33,11 @@ RUN mkdir --parents $NVM_DIR && \ RUN \. "$NVM_DIR/nvm.sh" && \ nvm install $NODE_VERSION && \ nvm use $NODE_VERSION && \ - npm install --global yarn && \ - yarn && \ - cd bskyembed && yarn install --frozen-lockfile && cd .. && \ - yarn intl:build && \ - yarn build-embed + npm install --global pnpm@11.7.0 && \ + pnpm install --frozen-lockfile && \ + cd bskyembed && pnpm install --frozen-lockfile && cd .. && \ + pnpm intl:build && \ + pnpm build-embed # DEBUG RUN find ./bskyweb/embedr-static && find ./bskyweb/embedr-templates && find ./bskyembed/dist diff --git a/Makefile b/Makefile index 5641314237..80a34e1172 100644 --- a/Makefile +++ b/Makefile @@ -10,33 +10,33 @@ help: ## Print info about all commands .PHONY: build-web build-web: ## Compile web bundle, copy to bskyweb directory - yarn intl:build - yarn build-web + pnpm intl:build + pnpm build-web .PHONY: build-web-embed build-web-embed: ## Compile web embed bundle, copy to bskyweb/embedr* directories - yarn intl:build - yarn build-embed + pnpm intl:build + pnpm build-embed .PHONY: test test: ## Run all tests - NODE_ENV=test yarn test + NODE_ENV=test pnpm test .PHONY: lint lint: ## Run style checks and verify syntax - yarn run lint + pnpm run lint #.PHONY: fmt #fmt: ## Run syntax re-formatting -# yarn prettier +# pnpm prettier .PHONY: deps -deps: ## Installs dependent libs using 'yarn install' - yarn install --frozen-lockfile +deps: ## Installs dependent libs using 'pnpm install' + pnpm install --frozen-lockfile cd bskyembed && yarn install --frozen-lockfile .PHONY: nvm-setup -nvm-setup: ## Use NVM to install and activate node+yarn +nvm-setup: ## Use NVM to install and activate node+pnpm nvm install 20 nvm use 20 - npm install --global yarn + npm install --global pnpm diff --git a/__e2e__/flows/curate-lists.yml b/__e2e__/flows/curate-lists.yml index 906199e979..cc97b0fd72 100644 --- a/__e2e__/flows/curate-lists.yml +++ b/__e2e__/flows/curate-lists.yml @@ -145,21 +145,21 @@ appId: xyz.blueskyweb.app id: "profileHeaderDropdownBtn" - tapOn: "Add to Lists" - assertVisible: - id: "userAddRemoveListsModal" + id: "userAddRemoveListsDialog" - tapOn: id: "user-bob.test-addBtn" - tapOn: id: "doneBtn" - assertNotVisible: - id: "userAddRemoveListsModal" + id: "userAddRemoveListsDialog" - tapOn: id: "profileHeaderDropdownBtn" - tapOn: "Add to Lists" - assertVisible: - id: "userAddRemoveListsModal" + id: "userAddRemoveListsDialog" - tapOn: id: "user-bob.test-addBtn" - tapOn: id: "doneBtn" - assertNotVisible: - id: "userAddRemoveListsModal" + id: "userAddRemoveListsDialog" diff --git a/__tests__/lib/images.test.ts b/__tests__/lib/images.test.ts index afe47c2793..f0f948365c 100644 --- a/__tests__/lib/images.test.ts +++ b/__tests__/lib/images.test.ts @@ -1,11 +1,12 @@ import {createDownloadResumable, deleteAsync} from 'expo-file-system/legacy' import {manipulateAsync, SaveFormat} from 'expo-image-manipulator' +import {IMAGE_SIZE_CONFIG_2K_1MB} from '../../src/lib/constants' import { downloadAndResize, type DownloadAndResizeOpts, - getResizedDimensions, } from '../../src/lib/media/manip' +import {getResizedDimensions} from '../../src/lib/media/util' const mockResizedImage = { path: 'file://resized-image.jpg', @@ -41,10 +42,8 @@ describe('downloadAndResize', () => { const opts: DownloadAndResizeOpts = { uri: 'https://example.com/image.jpg', - width: 100, - height: 100, + maxDimension: 2000, maxSize: 500000, - mode: 'cover', timeout: 10000, } @@ -60,9 +59,11 @@ describe('downloadAndResize', () => { // First time it gets called is to get dimensions expect(manipulateAsync).toHaveBeenCalledWith(expect.any(String), [], {}) + // The mocked source image is 100x100, below maxDimension, so it is not + // downsized. expect(manipulateAsync).toHaveBeenCalledWith( expect.any(String), - [{resize: {height: opts.height, width: opts.width}}], + [{resize: {height: 100, width: 100}}], {format: SaveFormat.JPEG, compress: 1.0}, ) expect(deleteAsync).toHaveBeenCalledWith(expect.any(String), { @@ -73,10 +74,8 @@ describe('downloadAndResize', () => { it('should return undefined for invalid URI', async () => { const opts: DownloadAndResizeOpts = { uri: 'invalid-uri', - width: 100, - height: 100, + maxDimension: 2000, maxSize: 500000, - mode: 'cover', timeout: 10000, } @@ -90,13 +89,19 @@ describe('downloadAndResize', () => { width: 1200, height: 1000, } - const resizedDimensionsOne = getResizedDimensions(initialDimensionsOne) + const resizedDimensionsOne = getResizedDimensions( + initialDimensionsOne, + IMAGE_SIZE_CONFIG_2K_1MB.maxDimension, + ) const initialDimensionsTwo = { width: 1000, height: 1200, } - const resizedDimensionsTwo = getResizedDimensions(initialDimensionsTwo) + const resizedDimensionsTwo = getResizedDimensions( + initialDimensionsTwo, + IMAGE_SIZE_CONFIG_2K_1MB.maxDimension, + ) expect(resizedDimensionsOne).toEqual(initialDimensionsOne) expect(resizedDimensionsTwo).toEqual(initialDimensionsTwo) @@ -107,13 +112,19 @@ describe('downloadAndResize', () => { width: 3000, height: 1500, } - const resizedDimensionsOne = getResizedDimensions(initialDimensionsOne) + const resizedDimensionsOne = getResizedDimensions( + initialDimensionsOne, + IMAGE_SIZE_CONFIG_2K_1MB.maxDimension, + ) const initialDimensionsTwo = { width: 2000, height: 4000, } - const resizedDimensionsTwo = getResizedDimensions(initialDimensionsTwo) + const resizedDimensionsTwo = getResizedDimensions( + initialDimensionsTwo, + IMAGE_SIZE_CONFIG_2K_1MB.maxDimension, + ) expect(resizedDimensionsOne).toEqual({ width: 2000, diff --git a/__tests__/lib/string.test.ts b/__tests__/lib/string.test.ts index 38966c35cb..8a56423796 100644 --- a/__tests__/lib/string.test.ts +++ b/__tests__/lib/string.test.ts @@ -8,8 +8,7 @@ import { parseStarterPackUri, } from '#/lib/strings/starter-pack' import {messages} from '#/locale/locales/en/messages' -import {klipyUrlToBskyGifUrl} from '#/state/queries/klipy' -import {tenorUrlToBskyGifUrl} from '#/state/queries/tenor' +import {klipyUrlToBskyGifUrl} from '#/features/gifPicker/utils' import {cleanError} from '../../src/lib/strings/errors' import {createFullHandle, makeValidHandle} from '../../src/lib/strings/handles' import {enforceLen} from '../../src/lib/strings/helpers' @@ -1072,21 +1071,6 @@ describe('createStarterPackGooglePlayUri', () => { }) }) -describe('tenorUrlToBskyGifUrl', () => { - const inputs = [ - 'https://media.tenor.com/someID_AAAAC/someName.gif', - 'https://media.tenor.com/someID/someName.gif', - ] - - it.each(inputs)( - 'returns url with t.gifs.bsky.app as hostname for input url', - input => { - const out = tenorUrlToBskyGifUrl(input) - expect(out.startsWith('https://t.gifs.bsky.app/')).toEqual(true) - }, - ) -}) - describe('klipyUrlToBskyGifUrl', () => { const inputs = [ 'https://static.klipy.com/ii/abc123/73/ac/someFile.gif', diff --git a/__tests__/lib/strings/url-helpers.test.ts b/__tests__/lib/strings/url-helpers.test.ts index 0b1b750281..dd1a096ef5 100644 --- a/__tests__/lib/strings/url-helpers.test.ts +++ b/__tests__/lib/strings/url-helpers.test.ts @@ -1,6 +1,7 @@ import {describe, expect, it} from '@jest/globals' import { + getChatInviteCodeFromUrl, isPossiblyAUrl, isTrustedUrl, linkRequiresWarning, @@ -178,3 +179,47 @@ describe('isTrustedUrl', () => { expect(output).toEqual(expected) }) }) + +describe('getChatInviteCodeFromUrl', () => { + type Case = [string, string | undefined] + + const cases: Case[] = [ + ['https://bsky.app/chat/abcdefg', 'abcdefg'], + ['https://bsky.app/chat/abcdefghij', 'abcdefghij'], + // http is not recognized as a bsky.app url + ['http://bsky.app/chat/abcdefg', undefined], + ['https://bsky.app/chat/abcdefg?utm=foo', 'abcdefg'], + ['https://bsky.app/chat/abcdefg#section', 'abcdefg'], + ['/chat/abcdefg', 'abcdefg'], + ['/chat/abcdefg?utm=foo', 'abcdefg'], + ['/chat/abcdefg#section', 'abcdefg'], + + // too short + ['https://bsky.app/chat/abcdef', undefined], + ['/chat/abcdef', undefined], + // too long + ['https://bsky.app/chat/abcdefghijk', undefined], + ['/chat/abcdefghijk', undefined], + // invalid characters + ['https://bsky.app/chat/abc-def', undefined], + ['/chat/abc def', undefined], + // trailing path + ['https://bsky.app/chat/abcdefg/extra', undefined], + ['/chat/abcdefg/extra', undefined], + // wrong path + ['https://bsky.app/profile/abcdefg', undefined], + ['https://bsky.app/chat', undefined], + // wrong host + ['https://example.com/chat/abcdefg', undefined], + // not a url, not a path + ['chat/abcdefg', undefined], + ['abcdefg', undefined], + ['', undefined], + // malformed url + ['https://[invalid/chat/abcdefg', undefined], + ] + + it.each(cases)('given input %p, returns %p', (input, expected) => { + expect(getChatInviteCodeFromUrl(input)).toEqual(expected) + }) +}) diff --git a/app.config.js b/app.config.js index 1f8971209a..790ae490eb 100644 --- a/app.config.js +++ b/app.config.js @@ -262,6 +262,7 @@ module.exports = function (_config) { deploymentTarget: '15.1', buildReactNativeFromSource: true, ccacheEnabled: IS_DEV, + cxxLanguageStandard: 'c++23', extraPods: [ { name: 'MCEmojiPicker', @@ -348,7 +349,7 @@ module.exports = function (_config) { }, ], [ - '@mozzius/expo-dynamic-app-icon', + '@bsky.app/expo-dynamic-app-icon', { /** * Default set diff --git a/assets/icons/arrowCornerDownRight_stroke2_rounded_2_rounded.svg b/assets/icons/arrowCornerDownRight_stroke2_corner2_rounded.svg similarity index 100% rename from assets/icons/arrowCornerDownRight_stroke2_rounded_2_rounded.svg rename to assets/icons/arrowCornerDownRight_stroke2_corner2_rounded.svg diff --git a/assets/icons/arrowCornerDownRight_stroke2_corner3_rounded.svg b/assets/icons/arrowCornerDownRight_stroke2_corner3_rounded.svg new file mode 100644 index 0000000000..ff7edc539e --- /dev/null +++ b/assets/icons/arrowCornerDownRight_stroke2_corner3_rounded.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/bubbleSmile_stroke2_corner0_rounded_large.svg b/assets/icons/bubbleSmile_stroke2_corner0_rounded_large.svg new file mode 100644 index 0000000000..5d0f9fddca --- /dev/null +++ b/assets/icons/bubbleSmile_stroke2_corner0_rounded_large.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/community/leaflet.svg b/assets/icons/community/leaflet.svg new file mode 100644 index 0000000000..ceeee657a5 --- /dev/null +++ b/assets/icons/community/leaflet.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/community/offprint.svg b/assets/icons/community/offprint.svg new file mode 100644 index 0000000000..fd07e5d0e6 --- /dev/null +++ b/assets/icons/community/offprint.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/community/pckt-full.svg b/assets/icons/community/pckt-full.svg new file mode 100644 index 0000000000..245c176f80 --- /dev/null +++ b/assets/icons/community/pckt-full.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/community/pckt.svg b/assets/icons/community/pckt.svg new file mode 100644 index 0000000000..a1b1764e0d --- /dev/null +++ b/assets/icons/community/pckt.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/community/standard-site.svg b/assets/icons/community/standard-site.svg new file mode 100644 index 0000000000..db86f6f7d7 --- /dev/null +++ b/assets/icons/community/standard-site.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/inbox_stroke2_corner2_rounded.svg b/assets/icons/inbox_stroke2_corner2_rounded.svg new file mode 100644 index 0000000000..a2cbed7ce7 --- /dev/null +++ b/assets/icons/inbox_stroke2_corner2_rounded.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/inbox_stroke2_corner2_rounded_large.svg b/assets/icons/inbox_stroke2_corner2_rounded_large.svg new file mode 100644 index 0000000000..0804046fa2 --- /dev/null +++ b/assets/icons/inbox_stroke2_corner2_rounded_large.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/language_stroke2_corner2_rounded.svg b/assets/icons/language_stroke2_corner2_rounded.svg new file mode 100644 index 0000000000..9eac8d1e0d --- /dev/null +++ b/assets/icons/language_stroke2_corner2_rounded.svg @@ -0,0 +1 @@ + diff --git a/assets/images/chat-desktop-bg-dark.webp b/assets/images/chat-desktop-bg-dark.webp new file mode 100644 index 0000000000..3a2b0f16c5 Binary files /dev/null and b/assets/images/chat-desktop-bg-dark.webp differ diff --git a/assets/images/chat-desktop-bg-dim.webp b/assets/images/chat-desktop-bg-dim.webp new file mode 100644 index 0000000000..9d82f5c05e Binary files /dev/null and b/assets/images/chat-desktop-bg-dim.webp differ diff --git a/assets/images/chat-desktop-bg-light.webp b/assets/images/chat-desktop-bg-light.webp new file mode 100644 index 0000000000..099a1045a3 Binary files /dev/null and b/assets/images/chat-desktop-bg-light.webp differ diff --git a/assets/images/chat-invite-friends.webp b/assets/images/chat-invite-friends.webp new file mode 100644 index 0000000000..536e3d6429 Binary files /dev/null and b/assets/images/chat-invite-friends.webp differ diff --git a/assets/images/chat-mobile-bg-dark.webp b/assets/images/chat-mobile-bg-dark.webp new file mode 100644 index 0000000000..7abab7c433 Binary files /dev/null and b/assets/images/chat-mobile-bg-dark.webp differ diff --git a/assets/images/chat-mobile-bg-dim.webp b/assets/images/chat-mobile-bg-dim.webp new file mode 100644 index 0000000000..1ed3c11051 Binary files /dev/null and b/assets/images/chat-mobile-bg-dim.webp differ diff --git a/assets/images/chat-mobile-bg-light.webp b/assets/images/chat-mobile-bg-light.webp new file mode 100644 index 0000000000..f14a816c70 Binary files /dev/null and b/assets/images/chat-mobile-bg-light.webp differ diff --git a/assets/images/groupchats_announcement_dark.webp b/assets/images/groupchats_announcement_dark.webp new file mode 100644 index 0000000000..5bbc64ebfc Binary files /dev/null and b/assets/images/groupchats_announcement_dark.webp differ diff --git a/assets/images/groupchats_announcement_dim.webp b/assets/images/groupchats_announcement_dim.webp new file mode 100644 index 0000000000..d418be4de0 Binary files /dev/null and b/assets/images/groupchats_announcement_dim.webp differ diff --git a/assets/images/groupchats_announcement_light.webp b/assets/images/groupchats_announcement_light.webp new file mode 100644 index 0000000000..32014850ad Binary files /dev/null and b/assets/images/groupchats_announcement_light.webp differ diff --git a/assets/images/invite_friends_announcement_nux.webp b/assets/images/invite_friends_announcement_nux.webp new file mode 100644 index 0000000000..53e79e1d54 Binary files /dev/null and b/assets/images/invite_friends_announcement_nux.webp differ diff --git a/assets/images/invite_friends_promo_banner.webp b/assets/images/invite_friends_promo_banner.webp new file mode 100644 index 0000000000..cb6d45e681 Binary files /dev/null and b/assets/images/invite_friends_promo_banner.webp differ diff --git a/babel.config.js b/babel.config.js index 63afeb3769..faf9b3e36b 100644 --- a/babel.config.js +++ b/babel.config.js @@ -47,6 +47,9 @@ module.exports = function (api) { production: { plugins: ['transform-remove-console'], }, + test: { + plugins: ['@babel/plugin-transform-class-static-block'], + }, }, } } diff --git a/bskyembed/README.md b/bskyembed/README.md index fe81026f11..d67e587947 100644 --- a/bskyembed/README.md +++ b/bskyembed/README.md @@ -4,13 +4,13 @@ Install dependencies: ```bash cd bskyembed -yarn +pnpm ``` Run the dev server: ```bash -yarn dev +pnpm dev ``` You can see the embed homepage at http://localhost:5173 @@ -20,7 +20,7 @@ You can see the embed homepage at http://localhost:5173 In another terminal window, run the snippet dev script: ```bash -yarn dev-snippet +pnpm dev-snippet ``` You can then see the testbed page at http://localhost:5173/test diff --git a/bskyembed/eslint.config.mjs b/bskyembed/eslint.config.mjs index 8cfa47df94..deb6f7623e 100644 --- a/bskyembed/eslint.config.mjs +++ b/bskyembed/eslint.config.mjs @@ -1,10 +1,12 @@ // @ts-check import js from '@eslint/js' +import {defineConfig} from 'eslint/config' import tseslint from 'typescript-eslint' import simpleImportSort from 'eslint-plugin-simple-import-sort' +import {importX} from 'eslint-plugin-import-x' import globals from 'globals' -export default tseslint.config( +export default defineConfig( // Global ignores { ignores: ['dist/**', 'node_modules/**'], @@ -14,7 +16,11 @@ export default tseslint.config( js.configs.recommended, // TypeScript rules with type checking - ...tseslint.configs.recommendedTypeChecked, + tseslint.configs.recommendedTypeChecked, + + // import-x + importX.flatConfigs.recommended, + importX.flatConfigs.typescript, // Main configuration { diff --git a/bskyembed/package.json b/bskyembed/package.json index b74adb8351..8baf4280d6 100644 --- a/bskyembed/package.json +++ b/bskyembed/package.json @@ -7,29 +7,33 @@ "dev-snippet": "tsc --project tsconfig.snippet.json && serve -s dist -p 3000 -n", "build": "tsc && vite build", "build-snippet": "tsc --project tsconfig.snippet.json", + "analyze": "ANALYZE=1 vite build", "lint": "eslint --cache src", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "format": "prettier -w src" }, "dependencies": { - "@atproto/api": "^0.15.25", + "@atproto/api": "0.20.11", "preact": "^10.4.8" }, "devDependencies": { - "@preact/preset-vite": "^2.10.2", - "@vitejs/plugin-legacy": "^7.0.0", "@eslint/js": "^9.18.0", + "@preact/preset-vite": "^2.10.2", + "@vitejs/plugin-legacy": "^8.0.1", "autoprefixer": "^10.4.19", "eslint": "^9.18.0", - "eslint-plugin-simple-import-sort": "^12.1.1", + "eslint-plugin-import-x": "^4.16.2", + "eslint-plugin-simple-import-sort": "^13.0.0", "globals": "^15.14.0", "postcss": "^8.4.38", - "typescript-eslint": "^8.20.0", + "prettier": "^3.8.3", "serve": "^14.2.5", "tailwindcss": "^3.4.3", "terser": "^5.43.1", - "typescript": "^5.8.3", - "vite": "^7.0.4", - "vite-tsconfig-paths": "^5.1.4" + "typescript": "^6.0.3", + "typescript-eslint": "^8.20.0", + "vite": "^8.0.16", + "vite-bundle-analyzer": "^1.3.8" }, "resolutions": { "@types/estree": "1.0.6" diff --git a/bskyembed/pnpm-lock.yaml b/bskyembed/pnpm-lock.yaml new file mode 100644 index 0000000000..2de86345d5 --- /dev/null +++ b/bskyembed/pnpm-lock.yaml @@ -0,0 +1,4826 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@atproto/api': + specifier: 0.20.11 + version: 0.20.11 + preact: + specifier: ^10.4.8 + version: 10.29.1 + devDependencies: + '@eslint/js': + specifier: ^9.18.0 + version: 9.39.4 + '@preact/preset-vite': + specifier: ^2.10.2 + version: 2.10.5(@babel/core@7.29.0)(preact@10.29.1)(vite@8.0.16(jiti@1.21.7)(terser@5.47.1)) + '@vitejs/plugin-legacy': + specifier: ^8.0.1 + version: 8.0.1(terser@5.47.1)(vite@8.0.16(jiti@1.21.7)(terser@5.47.1)) + autoprefixer: + specifier: ^10.4.19 + version: 10.5.0(postcss@8.5.14) + eslint: + specifier: ^9.18.0 + version: 9.39.4(jiti@1.21.7) + eslint-plugin-import-x: + specifier: ^4.16.2 + version: 4.16.2(@typescript-eslint/utils@8.59.3(eslint@9.39.4(jiti@1.21.7))(typescript@6.0.3))(eslint@9.39.4(jiti@1.21.7)) + eslint-plugin-simple-import-sort: + specifier: ^13.0.0 + version: 13.0.0(eslint@9.39.4(jiti@1.21.7)) + globals: + specifier: ^15.14.0 + version: 15.15.0 + postcss: + specifier: ^8.4.38 + version: 8.5.14 + prettier: + specifier: ^3.8.3 + version: 3.8.3 + serve: + specifier: ^14.2.5 + version: 14.2.6 + tailwindcss: + specifier: ^3.4.3 + version: 3.4.19 + terser: + specifier: ^5.43.1 + version: 5.47.1 + typescript: + specifier: ^6.0.3 + version: 6.0.3 + typescript-eslint: + specifier: ^8.20.0 + version: 8.59.3(eslint@9.39.4(jiti@1.21.7))(typescript@6.0.3) + vite: + specifier: ^8.0.16 + version: 8.0.16(jiti@1.21.7)(terser@5.47.1) + vite-bundle-analyzer: + specifier: ^1.3.8 + version: 1.3.8 + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@atproto/api@0.20.11': + resolution: {integrity: sha512-1NoVJpBDAdotxo1iMZdMd75JstpdKWgBYOnxfVD4m+52bRjgU4cFg3EOGNUognZntFgL/bIHyEgyN7SJWVf6Ig==} + engines: {node: '>=22'} + + '@atproto/common-web@0.5.0': + resolution: {integrity: sha512-ReWnkuZdDU/74/I47gaI26uxQjHmpq4edp41NnZZQ5vIIKGb7Ei6pZHzDTUD9JURo109SKrPx9RMP2IQm0fOKA==} + engines: {node: '>=22'} + + '@atproto/lex-data@0.1.1': + resolution: {integrity: sha512-/xza8nU/YhtzhETnHL3QKKofaJ28/0NCzhT7LaYoUkm8EgypWp5ykEtmW52yLhQM2JF6fVa25g1soQmNTGqtSg==} + engines: {node: '>=22'} + + '@atproto/lex-json@0.1.0': + resolution: {integrity: sha512-oWUrRMwFyWpmi/5k1Se3xBTbP06XdxBS5iFuUz9LmqItaPXwrWRD87a9ldPvINQ/A2/mn7J6/qug8sDVlhD+vQ==} + engines: {node: '>=22'} + + '@atproto/lexicon@0.7.1': + resolution: {integrity: sha512-voNfNED5KUxn3vpo7N5DMRblBDfWf7kSfdKhJFC1RrLCxg38YbBzzURNVQJ32bp13Oot8kYfyXBWxTgtKLvw8w==} + engines: {node: '>=22'} + + '@atproto/syntax@0.6.1': + resolution: {integrity: sha512-kA4dQDoMPpWCH8N0Q4KoSq024u5MkVfDVa8DdhyLjGA72z/khbOf1jXKPv7NIL2oEc9aj7geKELdvqyf4ogopA==} + engines: {node: '>=22'} + + '@atproto/xrpc@0.8.0': + resolution: {integrity: sha512-NJy02bIKrWlE2NQkRV1kT0Cj0ixbuxlF/MejBdo4cPWAa9v3oZexvAcjjb0zaOYeABkaU14iyIhvn2G4e/oLpw==} + engines: {node: '>=22'} + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.3': + resolution: {integrity: sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.29.3': + resolution: {integrity: sha512-RpLYy2sb51oNLjuu1iD3bwBqCBWUzjO0ocp+iaCP/lJtb2CPLcnC2Fftw+4sAzaMELGeWTgExSKADbdo0GFVzA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-create-regexp-features-plugin@7.28.5': + resolution: {integrity: sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-define-polyfill-provider@0.6.8': + resolution: {integrity: sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.28.5': + resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.27.1': + resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + + '@babel/helper-remap-async-to-generator@7.27.1': + resolution: {integrity: sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-replace-supers@7.28.6': + resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-wrap-function@7.28.6': + resolution: {integrity: sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.3': + resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5': + resolution: {integrity: sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1': + resolution: {integrity: sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1': + resolution: {integrity: sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.3': + resolution: {integrity: sha512-SRS46DFR4HqzUzCVgi90/xMoL+zeBDBvWdKYXSEzh79kXswNFEglUpMKxR04//dPqwYXWUBJ3mpUd933ru9Kmg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1': + resolution: {integrity: sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6': + resolution: {integrity: sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': + resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-assertions@7.28.6': + resolution: {integrity: sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.28.6': + resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.28.6': + resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6': + resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-arrow-functions@7.27.1': + resolution: {integrity: sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-generator-functions@7.29.0': + resolution: {integrity: sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-to-generator@7.28.6': + resolution: {integrity: sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoped-functions@7.27.1': + resolution: {integrity: sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoping@7.28.6': + resolution: {integrity: sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-properties@7.28.6': + resolution: {integrity: sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-static-block@7.28.6': + resolution: {integrity: sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + + '@babel/plugin-transform-classes@7.28.6': + resolution: {integrity: sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-computed-properties@7.28.6': + resolution: {integrity: sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-destructuring@7.28.5': + resolution: {integrity: sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-dotall-regex@7.28.6': + resolution: {integrity: sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-keys@7.27.1': + resolution: {integrity: sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0': + resolution: {integrity: sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-dynamic-import@7.27.1': + resolution: {integrity: sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-explicit-resource-management@7.28.6': + resolution: {integrity: sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-exponentiation-operator@7.28.6': + resolution: {integrity: sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-export-namespace-from@7.27.1': + resolution: {integrity: sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-for-of@7.27.1': + resolution: {integrity: sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-function-name@7.27.1': + resolution: {integrity: sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-json-strings@7.28.6': + resolution: {integrity: sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-literals@7.27.1': + resolution: {integrity: sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-logical-assignment-operators@7.28.6': + resolution: {integrity: sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-member-expression-literals@7.27.1': + resolution: {integrity: sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-amd@7.27.1': + resolution: {integrity: sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.28.6': + resolution: {integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-systemjs@7.29.4': + resolution: {integrity: sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-umd@7.27.1': + resolution: {integrity: sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0': + resolution: {integrity: sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-new-target@7.27.1': + resolution: {integrity: sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6': + resolution: {integrity: sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-numeric-separator@7.28.6': + resolution: {integrity: sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-rest-spread@7.28.6': + resolution: {integrity: sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-super@7.27.1': + resolution: {integrity: sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-catch-binding@7.28.6': + resolution: {integrity: sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-chaining@7.28.6': + resolution: {integrity: sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-parameters@7.27.7': + resolution: {integrity: sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-methods@7.28.6': + resolution: {integrity: sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-property-in-object@7.28.6': + resolution: {integrity: sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-property-literals@7.27.1': + resolution: {integrity: sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-development@7.27.1': + resolution: {integrity: sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx@7.28.6': + resolution: {integrity: sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regenerator@7.29.0': + resolution: {integrity: sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regexp-modifiers@7.28.6': + resolution: {integrity: sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-reserved-words@7.27.1': + resolution: {integrity: sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-shorthand-properties@7.27.1': + resolution: {integrity: sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-spread@7.28.6': + resolution: {integrity: sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-sticky-regex@7.27.1': + resolution: {integrity: sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-template-literals@7.27.1': + resolution: {integrity: sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typeof-symbol@7.27.1': + resolution: {integrity: sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-escapes@7.27.1': + resolution: {integrity: sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-property-regex@7.28.6': + resolution: {integrity: sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-regex@7.27.1': + resolution: {integrity: sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-sets-regex@7.28.6': + resolution: {integrity: sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/preset-env@7.29.5': + resolution: {integrity: sha512-/69t2aEzGKHD76DyLbHysF/QH2LJOB8iFnYO37unDTKBTubzcMRv0f3H5EiN1Q6ajOd/eB7dAInF0qdFVS06kA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-modules@0.1.6-no-external-plugins': + resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + + '@emnapi/core@1.11.1': + resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@emnapi/runtime@1.11.1': + resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.5': + resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.4': + resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/wasm-runtime@0.2.12': + resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} + + '@napi-rs/wasm-runtime@1.1.5': + resolution: {integrity: sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@oxc-project/types@0.133.0': + resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} + + '@package-json/types@0.0.12': + resolution: {integrity: sha512-uu43FGU34B5VM9mCNjXCwLaGHYjXdNincqKLaraaCW+7S2+SmiBg1Nv8bPnmschrIfZmfKNY9f3fC376MRrObw==} + + '@preact/preset-vite@2.10.5': + resolution: {integrity: sha512-p0vJpxiVO7KWWazWny3LUZ+saXyZKWv6Ju0bYMWNJRp2YveufRPgSUB1C4MTqGJfz07EehMgfN+AJNwQy+w6Iw==} + peerDependencies: + '@babel/core': 7.x + vite: 2.x || 3.x || 4.x || 5.x || 6.x || 7.x || 8.x + + '@prefresh/babel-plugin@0.5.3': + resolution: {integrity: sha512-57LX2SHs4BX2s1IwCjNzTE2OJeEepRCNf1VTEpbNcUyHfMO68eeOWGDIt4ob9aYlW6PEWZ1SuwNikuoIXANDtQ==} + + '@prefresh/core@1.5.10': + resolution: {integrity: sha512-7yPTFbG56sutaFu8krp3B4a200KOFUvrtlllKWRuLjsYXo9UUucHOZRcer+gtgMkFTpv6ob8TGcTwA32bSwa1w==} + peerDependencies: + preact: ^10.0.0 || ^11.0.0-0 + + '@prefresh/utils@1.2.1': + resolution: {integrity: sha512-vq/sIuN5nYfYzvyayXI4C2QkprfNaHUQ9ZX+3xLD8nL3rWyzpxOm1+K7RtMbhd+66QcaISViK7amjnheQ/4WZw==} + + '@prefresh/vite@2.4.12': + resolution: {integrity: sha512-FY1fzXpUjiuosznMV0YM7XAOPZjB5FIdWS0W24+XnlxYkt9hNAwwsiKYn+cuTEoMtD/ZVazS5QVssBr9YhpCQA==} + peerDependencies: + preact: ^10.4.0 || ^11.0.0-0 + vite: '>=2.0.0' + + '@rolldown/binding-android-arm64@1.0.3': + resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.3': + resolution: {integrity: sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.3': + resolution: {integrity: sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.3': + resolution: {integrity: sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + resolution: {integrity: sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.3': + resolution: {integrity: sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.3': + resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.0.3': + resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.3': + resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.3': + resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.3': + resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.3': + resolution: {integrity: sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + resolution: {integrity: sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.3': + resolution: {integrity: sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@rollup/pluginutils@4.2.1': + resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} + engines: {node: '>= 8.0.0'} + + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@tybys/wasm-util@0.10.2': + resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@typescript-eslint/eslint-plugin@8.59.3': + resolution: {integrity: sha512-PwFvSKsXGShKGW6n5bZOhGHEcCZXM8HofLK9fNsEwZXzFRjoY+XT1Vsf1zgyXdwTr0ZYz1/2tkZ0DBTT9jZjhw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.59.3 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.59.3': + resolution: {integrity: sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.59.3': + resolution: {integrity: sha512-ECiUWa/KYRGDFUqTNehaRgzDshnJfkTABJxVemHk4ko22gcr0ukloKjWvyQ64g8YCV/UI47kN1dbmjf/GaQYng==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.59.3': + resolution: {integrity: sha512-t2LvZnoEfzKtnPjgeEu41xw5gxq9mQVfYy4OoZ4Vlt0sk3JwxmhCca/AR7DwOiHrjWgjAj6as4AhRLKSDfvZIA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.59.3': + resolution: {integrity: sha512-PcIJHjmaREXLgIAIzLnSY9VucEzz8FKXsRgFa1DmdGCK/5tJpW03TKJF01Q6VZd1lLdz2sIKPWaDUZN9dp//dw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.59.3': + resolution: {integrity: sha512-g71d8QD8UaiHGvrJwyIS1hCX5r63w6Jll+4VEYhEAHXTDIqX1JgxhTAbEHtKntL9kuc4jRo7/GWw5xfCepSccQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.59.3': + resolution: {integrity: sha512-ePFoH0g4ludssdRFqqDxQePCxU4WQyRa9+XVwjm7yLn0FKhMeoetC+qBEEI1Eyb1pGSDveTIT09Bvw2WhlGayg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.59.3': + resolution: {integrity: sha512-CbRjVRAf7Lr9Kr8RopKcbY45p2VfmmHrm0ygOCYFi7oU8q19m0Fs/6iHS7kNOmwpp+ob07ZVcAqlxUod9lYdmg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.59.3': + resolution: {integrity: sha512-JAvT14goBzRzzzZyqq3P9BLArIxTtQURUtFgQ/V7FO+eU+Gg6ES+5ymOPP1wRxXcxAYeivCk4uS3jCKWI1K8Zg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.59.3': + resolution: {integrity: sha512-f1UQF7ggd42YiwI5wGrRaPsa+P0CINBlrkLPmGfpq/u/I/oVtecoEIfFR9ag/oa1sLOsRNZ6xehf6qMZhQGBDg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@unrs/resolver-binding-android-arm-eabi@1.11.1': + resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} + cpu: [arm] + os: [android] + + '@unrs/resolver-binding-android-arm64@1.11.1': + resolution: {integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==} + cpu: [arm64] + os: [android] + + '@unrs/resolver-binding-darwin-arm64@1.11.1': + resolution: {integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==} + cpu: [arm64] + os: [darwin] + + '@unrs/resolver-binding-darwin-x64@1.11.1': + resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==} + cpu: [x64] + os: [darwin] + + '@unrs/resolver-binding-freebsd-x64@1.11.1': + resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==} + cpu: [x64] + os: [freebsd] + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': + resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': + resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': + resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-arm64-musl@1.11.1': + resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': + resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': + resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': + resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': + resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-x64-gnu@1.11.1': + resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-x64-musl@1.11.1': + resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-wasm32-wasi@1.11.1': + resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': + resolution: {integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==} + cpu: [arm64] + os: [win32] + + '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': + resolution: {integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==} + cpu: [ia32] + os: [win32] + + '@unrs/resolver-binding-win32-x64-msvc@1.11.1': + resolution: {integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==} + cpu: [x64] + os: [win32] + + '@vitejs/plugin-legacy@8.0.1': + resolution: {integrity: sha512-8zeDeuNPqXd49rIVgFgluQYB8vQICHR7l+W2I3CxYK4gTjTorajVr0wLvSjALIwEwLRxBn68EgNVyGP4j6hP7w==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + terser: ^5.16.0 + vite: ^8.0.0 + + '@zeit/schemas@2.36.0': + resolution: {integrity: sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + + ansi-align@3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arch@2.2.0: + resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + autoprefixer@10.5.0: + resolution: {integrity: sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + await-lock@3.0.0: + resolution: {integrity: sha512-eO6fLiSnrJrMdjWMNK8zbVRXPs2TKJg78iKZd9wDpN3na5tcoV6EoeiOlMgk2QaAQ1gIrK1YuMsJHXWqz89tSA==} + + babel-plugin-polyfill-corejs2@0.4.17: + resolution: {integrity: sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.14.2: + resolution: {integrity: sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-regenerator@0.6.8: + resolution: {integrity: sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-transform-hook-names@1.0.2: + resolution: {integrity: sha512-5gafyjyyBTTdX/tQQ0hRgu4AhNHG/hqWi0ZZmg2xvs2FgRkJXzDNKBZCyoYqgFkovfDrgM8OoKg8karoUvWeCw==} + peerDependencies: + '@babel/core': ^7.12.10 + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + baseline-browser-mapping@2.10.37: + resolution: {integrity: sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig==} + engines: {node: '>=6.0.0'} + hasBin: true + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + boxen@7.0.0: + resolution: {integrity: sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==} + engines: {node: '>=14.16'} + + brace-expansion@1.1.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} + + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist-to-esbuild@2.1.1: + resolution: {integrity: sha512-KN+mty6C3e9AN8Z5dI1xeN15ExcRNeISoC3g7V0Kax/MMF9MSoYA2G7lkTTcVUFntiEjkpI0HNgqJC1NjdyNUw==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + browserslist: '*' + + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + bytes@3.0.0: + resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} + engines: {node: '>= 0.8'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + camelcase@7.0.1: + resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} + engines: {node: '>=14.16'} + + caniuse-lite@1.0.30001792: + resolution: {integrity: sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==} + + caniuse-lite@1.0.30001799: + resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==} + + chalk-template@0.4.0: + resolution: {integrity: sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==} + engines: {node: '>=12'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.0.1: + resolution: {integrity: sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + cli-boxes@3.0.0: + resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} + engines: {node: '>=10'} + + clipboardy@3.0.0: + resolution: {integrity: sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + comment-parser@1.4.6: + resolution: {integrity: sha512-ObxuY6vnbWTN6Od72xfwN9DbzC7Y2vv8u1Soi9ahRKL37gb6y1qk6/dgjs+3JWuXJHWvsg3BXIwzd/rkmAwavg==} + engines: {node: '>= 12.0.0'} + + compressible@2.0.18: + resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} + engines: {node: '>= 0.6'} + + compression@1.8.1: + resolution: {integrity: sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==} + engines: {node: '>= 0.8.0'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + content-disposition@0.5.2: + resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==} + engines: {node: '>= 0.6'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + core-js-compat@3.49.0: + resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} + + core-js@3.49.0: + resolution: {integrity: sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-select@5.2.2: + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + electron-to-chromium@1.5.372: + resolution: {integrity: sha512-M3yhbAlilnwqC8D21t28UCDGHyitShTmmLRU/H+b74P6Ski16Nb9HONYEaVpMj/pwC7BEo5B95FpjODLCWbtfA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-import-context@0.1.9: + resolution: {integrity: sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + peerDependencies: + unrs-resolver: ^1.0.0 + peerDependenciesMeta: + unrs-resolver: + optional: true + + eslint-plugin-import-x@4.16.2: + resolution: {integrity: sha512-rM9K8UBHcWKpzQzStn1YRN2T5NvdeIfSVoKu/lKF41znQXHAUcBbYXe5wd6GNjZjTrP7viQ49n1D83x/2gYgIw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/utils': ^8.56.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + eslint-import-resolver-node: '*' + peerDependenciesMeta: + '@typescript-eslint/utils': + optional: true + eslint-import-resolver-node: + optional: true + + eslint-plugin-simple-import-sort@13.0.0: + resolution: {integrity: sha512-McAc+/Nlvcg4byY/CABGH8kqnefWBj8s3JA2okEtz8ixbECQgU46p0HkTUKa4YS7wvgGceimlc34p1nXqbWqtA==} + peerDependencies: + eslint: '>=5.0.0' + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@9.39.4: + resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-uri@3.1.2: + resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@15.15.0: + resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} + engines: {node: '>=18'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hasown@2.0.3: + resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} + engines: {node: '>= 0.4'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} + + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-port-reachable@4.0.0: + resolution: {integrity: sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + iso-datestring-validator@2.2.2: + resolution: {integrity: sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==} + + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kolorist@1.8.0: + resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + meow@13.2.0: + resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} + engines: {node: '>=18'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.33.0: + resolution: {integrity: sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==} + engines: {node: '>= 0.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.18: + resolution: {integrity: sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==} + engines: {node: '>= 0.6'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + multiformats@13.4.2: + resolution: {integrity: sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + napi-postinstall@0.3.4: + resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + negotiator@0.6.4: + resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} + engines: {node: '>= 0.6'} + + node-html-parser@6.1.13: + resolution: {integrity: sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==} + + node-releases@2.0.47: + resolution: {integrity: sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==} + engines: {node: '>=18'} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + on-headers@1.1.0: + resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==} + engines: {node: '>= 0.8'} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-inside@1.0.2: + resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-to-regexp@3.3.0: + resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.1.0: + resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.14: + resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} + engines: {node: ^10 || ^12 || >=14} + + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + + preact@10.29.1: + resolution: {integrity: sha512-gQCLc/vWroE8lIpleXtdJhTFDogTdZG9AjMUpVkDf2iTCNwYNWA+u16dL41TqUDJO4gm2IgrcMv3uTpjd4Pwmg==} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@3.8.3: + resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} + engines: {node: '>=14'} + hasBin: true + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + range-parser@1.2.0: + resolution: {integrity: sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==} + engines: {node: '>= 0.6'} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + regenerate-unicode-properties@10.2.2: + resolution: {integrity: sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==} + engines: {node: '>=4'} + + regenerate@1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + + regexpu-core@6.4.0: + resolution: {integrity: sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==} + engines: {node: '>=4'} + + registry-auth-token@3.3.2: + resolution: {integrity: sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==} + + registry-url@3.1.0: + resolution: {integrity: sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==} + engines: {node: '>=0.10.0'} + + regjsgen@0.8.0: + resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} + + regjsparser@0.13.1: + resolution: {integrity: sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==} + hasBin: true + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + engines: {node: '>= 0.4'} + hasBin: true + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rolldown@1.0.3: + resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.8.0: + resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} + engines: {node: '>=10'} + hasBin: true + + semver@7.8.4: + resolution: {integrity: sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==} + engines: {node: '>=10'} + hasBin: true + + serve-handler@6.1.7: + resolution: {integrity: sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==} + + serve@14.2.6: + resolution: {integrity: sha512-QEjUSA+sD4Rotm1znR8s50YqA3kYpRGPmtd5GlFxbaL9n/FdUNbqMhxClqdditSk0LlZyA/dhud6XNRTOC9x2Q==} + engines: {node: '>= 14'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + simple-code-frame@1.3.0: + resolution: {integrity: sha512-MB4pQmETUBlNs62BBeRjIFGeuy/x6gGKh7+eRUemn1rCFhqo7K+4slPqsyizCbcbYLnaYqaoZ2FWsZ/jN06D8w==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + stable-hash-x@0.2.0: + resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==} + engines: {node: '>=12.0.0'} + + stack-trace@1.0.0: + resolution: {integrity: sha512-H6D7134xi6qONvh7ZHKgviXf+rd3vhGBSvebPZCaUkd8zvQ+7PtDw6CljPTe4cXWNf2IKZGNqw6VJXSb9IgBpA==} + engines: {node: '>=20.0.0'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + systemjs@6.15.1: + resolution: {integrity: sha512-Nk8c4lXvMB98MtbmjX7JwJRgJOL8fluecYCfCeYBznwmpOs8Bf15hLM6z4z71EDAhQVrQrI+wt1aLWSXZq+hXA==} + + tailwindcss@3.4.19: + resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==} + engines: {node: '>=14.0.0'} + hasBin: true + + terser@5.47.1: + resolution: {integrity: sha512-tPbLXTI6ohPASb/1YViL428oEHu6/qv1OxqYnfaonVCFHqx4+wCd95pHrQWsL5X4pl90CTyW9piSAsS2L0VoMw==} + engines: {node: '>=10'} + hasBin: true + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tlds@1.261.0: + resolution: {integrity: sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA==} + hasBin: true + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + + typescript-eslint@8.59.3: + resolution: {integrity: sha512-KgusgyDgG4LI8Ih/sWaCtZ06tckLAS5CvT5A4D1Q7bYVoAAyzwiZvE4BmwDHkhRVkvhRBepKeASoFzQetha7Fg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + uint8arrays@5.1.1: + resolution: {integrity: sha512-9muQwa4wZG4dKi9gMAIBtnk2Pw87SRpvWTH6lOGm19V2Uqxr4uomUf2PGqPnWc+qs06sN8owUU4jfcoWOcfwVQ==} + + unicode-canonical-property-names-ecmascript@2.0.1: + resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} + engines: {node: '>=4'} + + unicode-match-property-ecmascript@2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} + engines: {node: '>=4'} + + unicode-match-property-value-ecmascript@2.2.1: + resolution: {integrity: sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==} + engines: {node: '>=4'} + + unicode-property-aliases-ecmascript@2.2.0: + resolution: {integrity: sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==} + engines: {node: '>=4'} + + unicode-segmenter@0.14.5: + resolution: {integrity: sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g==} + + unrs-resolver@1.11.1: + resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + update-check@1.5.4: + resolution: {integrity: sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vite-bundle-analyzer@1.3.8: + resolution: {integrity: sha512-IIk7WPhoYs7pyo75jwI+dFt7yykgjK7NY+dqnJtiZnyqP2k6NgPb3TY80FLFjtgnfk/o+OjI18+anKyeviCbRA==} + hasBin: true + + vite-prerender-plugin@0.5.13: + resolution: {integrity: sha512-IKSpYkzDBsKAxa05naRbj7GvNVMSdww/Z/E89oO3xndz+gWnOBOKOAbEXv7qDhktY/j3vHgJmoV1pPzqU2tx9g==} + peerDependencies: + vite: 5.x || 6.x || 7.x || 8.x + + vite@8.0.16: + resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.18 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + widest-line@4.0.1: + resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==} + engines: {node: '>=12'} + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zimmerframe@1.1.4: + resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@atproto/api@0.20.11': + dependencies: + '@atproto/common-web': 0.5.0 + '@atproto/lexicon': 0.7.1 + '@atproto/syntax': 0.6.1 + '@atproto/xrpc': 0.8.0 + await-lock: 3.0.0 + multiformats: 13.4.2 + tlds: 1.261.0 + zod: 3.25.76 + + '@atproto/common-web@0.5.0': + dependencies: + '@atproto/lex-data': 0.1.1 + '@atproto/lex-json': 0.1.0 + '@atproto/syntax': 0.6.1 + zod: 3.25.76 + + '@atproto/lex-data@0.1.1': + dependencies: + multiformats: 13.4.2 + tslib: 2.8.1 + uint8arrays: 5.1.1 + unicode-segmenter: 0.14.5 + + '@atproto/lex-json@0.1.0': + dependencies: + '@atproto/lex-data': 0.1.1 + tslib: 2.8.1 + + '@atproto/lexicon@0.7.1': + dependencies: + '@atproto/common-web': 0.5.0 + '@atproto/syntax': 0.6.1 + multiformats: 13.4.2 + zod: 3.25.76 + + '@atproto/syntax@0.6.1': + dependencies: + iso-datestring-validator: 2.2.2 + tslib: 2.8.1 + + '@atproto/xrpc@0.8.0': + dependencies: + '@atproto/lexicon': 0.7.1 + zod: 3.25.76 + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.3': {} + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.0) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.3 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/generator@7.29.7': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.29.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.3 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.29.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.29.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + regexpu-core: 6.4.0 + semver: 6.3.1 + + '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + debug: 4.4.3 + lodash.debounce: 4.0.8 + resolve: 1.22.12 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-member-expression-to-functions@7.28.5': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.27.1': + dependencies: + '@babel/types': 7.29.0 + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-wrap-function': 7.28.6 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helper-wrap-function@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/parser@7.29.3': + dependencies: + '@babel/types': 7.29.0 + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + + '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-classes@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-globals': 7.28.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/template': 7.28.6 + + '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-literals@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-systemjs@7.29.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-jsx@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-spread@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/preset-env@7.29.5(@babel/core@7.29.0)': + dependencies: + '@babel/compat-data': 7.29.3 + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array': 7.29.3(@babel/core@7.29.0) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0) + '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.0) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-modules-systemjs': 7.29.4(@babel/core@7.29.0) + '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) + '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.29.0) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.0) + babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.0) + babel-plugin-polyfill-corejs3: 0.14.2(@babel/core@7.29.0) + babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.0) + core-js-compat: 3.49.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/types': 7.29.0 + esutils: 2.0.3 + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.3 + '@babel/types': 7.29.0 + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.3 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/traverse@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/core@1.11.1': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@1.21.7))': + dependencies: + eslint: 9.39.4(jiti@1.21.7) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.2': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.5': + dependencies: + ajv: 6.15.0 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.4': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/source-map@0.3.11': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/wasm-runtime@0.2.12': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.2 + optional: true + + '@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.2 + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@oxc-project/types@0.133.0': {} + + '@package-json/types@0.0.12': {} + + '@preact/preset-vite@2.10.5(@babel/core@7.29.0)(preact@10.29.1)(vite@8.0.16(jiti@1.21.7)(terser@5.47.1))': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.29.0) + '@prefresh/vite': 2.4.12(preact@10.29.1)(vite@8.0.16(jiti@1.21.7)(terser@5.47.1)) + '@rollup/pluginutils': 5.3.0 + babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.29.0) + debug: 4.4.3 + magic-string: 0.30.21 + picocolors: 1.1.1 + vite: 8.0.16(jiti@1.21.7)(terser@5.47.1) + vite-prerender-plugin: 0.5.13(vite@8.0.16(jiti@1.21.7)(terser@5.47.1)) + zimmerframe: 1.1.4 + transitivePeerDependencies: + - preact + - rollup + - supports-color + + '@prefresh/babel-plugin@0.5.3': {} + + '@prefresh/core@1.5.10(preact@10.29.1)': + dependencies: + preact: 10.29.1 + + '@prefresh/utils@1.2.1': {} + + '@prefresh/vite@2.4.12(preact@10.29.1)(vite@8.0.16(jiti@1.21.7)(terser@5.47.1))': + dependencies: + '@babel/core': 7.29.0 + '@prefresh/babel-plugin': 0.5.3 + '@prefresh/core': 1.5.10(preact@10.29.1) + '@prefresh/utils': 1.2.1 + '@rollup/pluginutils': 4.2.1 + preact: 10.29.1 + vite: 8.0.16(jiti@1.21.7)(terser@5.47.1) + transitivePeerDependencies: + - supports-color + + '@rolldown/binding-android-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-x64@1.0.3': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.3': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.3': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.3': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.3': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.3': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.3': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@rollup/pluginutils@4.2.1': + dependencies: + estree-walker: 2.0.2 + picomatch: 2.3.2 + + '@rollup/pluginutils@5.3.0': + dependencies: + '@types/estree': 1.0.9 + estree-walker: 2.0.2 + picomatch: 4.0.4 + + '@tybys/wasm-util@0.10.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/estree@1.0.9': {} + + '@types/json-schema@7.0.15': {} + + '@typescript-eslint/eslint-plugin@8.59.3(@typescript-eslint/parser@8.59.3(eslint@9.39.4(jiti@1.21.7))(typescript@6.0.3))(eslint@9.39.4(jiti@1.21.7))(typescript@6.0.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.59.3(eslint@9.39.4(jiti@1.21.7))(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.59.3 + '@typescript-eslint/type-utils': 8.59.3(eslint@9.39.4(jiti@1.21.7))(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.3(eslint@9.39.4(jiti@1.21.7))(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.59.3 + eslint: 9.39.4(jiti@1.21.7) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.59.3(eslint@9.39.4(jiti@1.21.7))(typescript@6.0.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.59.3 + '@typescript-eslint/types': 8.59.3 + '@typescript-eslint/typescript-estree': 8.59.3(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.59.3 + debug: 4.4.3 + eslint: 9.39.4(jiti@1.21.7) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.59.3(typescript@6.0.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.59.3(typescript@6.0.3) + '@typescript-eslint/types': 8.59.3 + debug: 4.4.3 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.59.3': + dependencies: + '@typescript-eslint/types': 8.59.3 + '@typescript-eslint/visitor-keys': 8.59.3 + + '@typescript-eslint/tsconfig-utils@8.59.3(typescript@6.0.3)': + dependencies: + typescript: 6.0.3 + + '@typescript-eslint/type-utils@8.59.3(eslint@9.39.4(jiti@1.21.7))(typescript@6.0.3)': + dependencies: + '@typescript-eslint/types': 8.59.3 + '@typescript-eslint/typescript-estree': 8.59.3(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.3(eslint@9.39.4(jiti@1.21.7))(typescript@6.0.3) + debug: 4.4.3 + eslint: 9.39.4(jiti@1.21.7) + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.59.3': {} + + '@typescript-eslint/typescript-estree@8.59.3(typescript@6.0.3)': + dependencies: + '@typescript-eslint/project-service': 8.59.3(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.59.3(typescript@6.0.3) + '@typescript-eslint/types': 8.59.3 + '@typescript-eslint/visitor-keys': 8.59.3 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.8.4 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.59.3(eslint@9.39.4(jiti@1.21.7))(typescript@6.0.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@1.21.7)) + '@typescript-eslint/scope-manager': 8.59.3 + '@typescript-eslint/types': 8.59.3 + '@typescript-eslint/typescript-estree': 8.59.3(typescript@6.0.3) + eslint: 9.39.4(jiti@1.21.7) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.59.3': + dependencies: + '@typescript-eslint/types': 8.59.3 + eslint-visitor-keys: 5.0.1 + + '@unrs/resolver-binding-android-arm-eabi@1.11.1': + optional: true + + '@unrs/resolver-binding-android-arm64@1.11.1': + optional: true + + '@unrs/resolver-binding-darwin-arm64@1.11.1': + optional: true + + '@unrs/resolver-binding-darwin-x64@1.11.1': + optional: true + + '@unrs/resolver-binding-freebsd-x64@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-x64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-x64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-wasm32-wasi@1.11.1': + dependencies: + '@napi-rs/wasm-runtime': 0.2.12 + optional: true + + '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': + optional: true + + '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': + optional: true + + '@unrs/resolver-binding-win32-x64-msvc@1.11.1': + optional: true + + '@vitejs/plugin-legacy@8.0.1(terser@5.47.1)(vite@8.0.16(jiti@1.21.7)(terser@5.47.1))': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-modules-systemjs': 7.29.4(@babel/core@7.29.0) + '@babel/preset-env': 7.29.5(@babel/core@7.29.0) + babel-plugin-polyfill-corejs3: 0.14.2(@babel/core@7.29.0) + babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.0) + browserslist: 4.28.2 + browserslist-to-esbuild: 2.1.1(browserslist@4.28.2) + core-js: 3.49.0 + magic-string: 0.30.21 + regenerator-runtime: 0.14.1 + systemjs: 6.15.1 + terser: 5.47.1 + vite: 8.0.16(jiti@1.21.7)(terser@5.47.1) + transitivePeerDependencies: + - supports-color + + '@zeit/schemas@2.36.0': {} + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.18.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.2 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-align@3.0.1: + dependencies: + string-width: 4.2.3 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.2 + + arch@2.2.0: {} + + arg@5.0.2: {} + + argparse@2.0.1: {} + + autoprefixer@10.5.0(postcss@8.5.14): + dependencies: + browserslist: 4.28.2 + caniuse-lite: 1.0.30001792 + fraction.js: 5.3.4 + picocolors: 1.1.1 + postcss: 8.5.14 + postcss-value-parser: 4.2.0 + + await-lock@3.0.0: {} + + babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.0): + dependencies: + '@babel/compat-data': 7.29.3 + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-corejs3@0.14.2(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + core-js-compat: 3.49.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + babel-plugin-transform-hook-names@1.0.2(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + + balanced-match@1.0.2: {} + + balanced-match@4.0.4: {} + + baseline-browser-mapping@2.10.37: {} + + binary-extensions@2.3.0: {} + + boolbase@1.0.0: {} + + boxen@7.0.0: + dependencies: + ansi-align: 3.0.1 + camelcase: 7.0.1 + chalk: 5.0.1 + cli-boxes: 3.0.0 + string-width: 5.1.2 + type-fest: 2.19.0 + widest-line: 4.0.1 + wrap-ansi: 8.1.0 + + brace-expansion@1.1.14: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@5.0.6: + dependencies: + balanced-match: 4.0.4 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist-to-esbuild@2.1.1(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + meow: 13.2.0 + + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.37 + caniuse-lite: 1.0.30001799 + electron-to-chromium: 1.5.372 + node-releases: 2.0.47 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + + buffer-from@1.1.2: {} + + bytes@3.0.0: {} + + bytes@3.1.2: {} + + callsites@3.1.0: {} + + camelcase-css@2.0.1: {} + + camelcase@7.0.1: {} + + caniuse-lite@1.0.30001792: {} + + caniuse-lite@1.0.30001799: {} + + chalk-template@0.4.0: + dependencies: + chalk: 4.1.2 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.0.1: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + cli-boxes@3.0.0: {} + + clipboardy@3.0.0: + dependencies: + arch: 2.2.0 + execa: 5.1.1 + is-wsl: 2.2.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@2.20.3: {} + + commander@4.1.1: {} + + comment-parser@1.4.6: {} + + compressible@2.0.18: + dependencies: + mime-db: 1.54.0 + + compression@1.8.1: + dependencies: + bytes: 3.1.2 + compressible: 2.0.18 + debug: 2.6.9 + negotiator: 0.6.4 + on-headers: 1.1.0 + safe-buffer: 5.2.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + concat-map@0.0.1: {} + + content-disposition@0.5.2: {} + + convert-source-map@2.0.0: {} + + core-js-compat@3.49.0: + dependencies: + browserslist: 4.28.2 + + core-js@3.49.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-select@5.2.2: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-what@6.2.2: {} + + cssesc@3.0.0: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-extend@0.6.0: {} + + deep-is@0.1.4: {} + + detect-libc@2.1.2: {} + + didyoumean@1.2.2: {} + + dlv@1.1.3: {} + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + eastasianwidth@0.2.0: {} + + electron-to-chromium@1.5.372: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + entities@4.5.0: {} + + es-errors@1.3.0: {} + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-import-context@0.1.9(unrs-resolver@1.11.1): + dependencies: + get-tsconfig: 4.14.0 + stable-hash-x: 0.2.0 + optionalDependencies: + unrs-resolver: 1.11.1 + + eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.59.3(eslint@9.39.4(jiti@1.21.7))(typescript@6.0.3))(eslint@9.39.4(jiti@1.21.7)): + dependencies: + '@package-json/types': 0.0.12 + '@typescript-eslint/types': 8.59.3 + comment-parser: 1.4.6 + debug: 4.4.3 + eslint: 9.39.4(jiti@1.21.7) + eslint-import-context: 0.1.9(unrs-resolver@1.11.1) + is-glob: 4.0.3 + minimatch: 10.2.5 + semver: 7.8.0 + stable-hash-x: 0.2.0 + unrs-resolver: 1.11.1 + optionalDependencies: + '@typescript-eslint/utils': 8.59.3(eslint@9.39.4(jiti@1.21.7))(typescript@6.0.3) + transitivePeerDependencies: + - supports-color + + eslint-plugin-simple-import-sort@13.0.0(eslint@9.39.4(jiti@1.21.7)): + dependencies: + eslint: 9.39.4(jiti@1.21.7) + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@9.39.4(jiti@1.21.7): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@1.21.7)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.2 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.5 + '@eslint/js': 9.39.4 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 + ajv: 6.15.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 1.21.7 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + esutils@2.0.3: {} + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-uri@3.1.2: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + + fraction.js@5.3.4: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-stream@6.0.1: {} + + get-tsconfig@4.14.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + globals@15.15.0: {} + + has-flag@4.0.0: {} + + hasown@2.0.3: + dependencies: + function-bind: 1.1.2 + + he@1.2.0: {} + + human-signals@2.1.0: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + ini@1.3.8: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-core-module@2.16.2: + dependencies: + hasown: 2.0.3 + + is-docker@2.2.1: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-port-reachable@4.0.0: {} + + is-stream@2.0.1: {} + + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + + isexe@2.0.0: {} + + iso-datestring-validator@2.2.2: {} + + jiti@1.21.7: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kolorist@1.8.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.debounce@4.0.8: {} + + lodash.merge@4.6.2: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + meow@13.2.0: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + mime-db@1.33.0: {} + + mime-db@1.54.0: {} + + mime-types@2.1.18: + dependencies: + mime-db: 1.33.0 + + mimic-fn@2.1.0: {} + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.6 + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.14 + + minimist@1.2.8: {} + + ms@2.0.0: {} + + ms@2.1.3: {} + + multiformats@13.4.2: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.12: {} + + napi-postinstall@0.3.4: {} + + natural-compare@1.4.0: {} + + negotiator@0.6.4: {} + + node-html-parser@6.1.13: + dependencies: + css-select: 5.2.2 + he: 1.2.0 + + node-releases@2.0.47: {} + + normalize-path@3.0.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + on-headers@1.1.0: {} + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-is-inside@1.0.2: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-to-regexp@3.3.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.4: {} + + pify@2.3.0: {} + + pirates@4.0.7: {} + + postcss-import@15.1.0(postcss@8.5.14): + dependencies: + postcss: 8.5.14 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.12 + + postcss-js@4.1.0(postcss@8.5.14): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.5.14 + + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.14): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 1.21.7 + postcss: 8.5.14 + + postcss-nested@6.2.0(postcss@8.5.14): + dependencies: + postcss: 8.5.14 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.14: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postcss@8.5.15: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + preact@10.29.1: {} + + prelude-ls@1.2.1: {} + + prettier@3.8.3: {} + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + range-parser@1.2.0: {} + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.2 + + regenerate-unicode-properties@10.2.2: + dependencies: + regenerate: 1.4.2 + + regenerate@1.4.2: {} + + regenerator-runtime@0.14.1: {} + + regexpu-core@6.4.0: + dependencies: + regenerate: 1.4.2 + regenerate-unicode-properties: 10.2.2 + regjsgen: 0.8.0 + regjsparser: 0.13.1 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.2.1 + + registry-auth-token@3.3.2: + dependencies: + rc: 1.2.8 + safe-buffer: 5.2.1 + + registry-url@3.1.0: + dependencies: + rc: 1.2.8 + + regjsgen@0.8.0: {} + + regjsparser@0.13.1: + dependencies: + jsesc: 3.1.0 + + require-from-string@2.0.2: {} + + resolve-from@4.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + resolve@1.22.12: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.2 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + rolldown@1.0.3: + dependencies: + '@oxc-project/types': 0.133.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.3 + '@rolldown/binding-darwin-arm64': 1.0.3 + '@rolldown/binding-darwin-x64': 1.0.3 + '@rolldown/binding-freebsd-x64': 1.0.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.3 + '@rolldown/binding-linux-arm64-gnu': 1.0.3 + '@rolldown/binding-linux-arm64-musl': 1.0.3 + '@rolldown/binding-linux-ppc64-gnu': 1.0.3 + '@rolldown/binding-linux-s390x-gnu': 1.0.3 + '@rolldown/binding-linux-x64-gnu': 1.0.3 + '@rolldown/binding-linux-x64-musl': 1.0.3 + '@rolldown/binding-openharmony-arm64': 1.0.3 + '@rolldown/binding-wasm32-wasi': 1.0.3 + '@rolldown/binding-win32-arm64-msvc': 1.0.3 + '@rolldown/binding-win32-x64-msvc': 1.0.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-buffer@5.2.1: {} + + semver@6.3.1: {} + + semver@7.8.0: {} + + semver@7.8.4: {} + + serve-handler@6.1.7: + dependencies: + bytes: 3.0.0 + content-disposition: 0.5.2 + mime-types: 2.1.18 + minimatch: 3.1.5 + path-is-inside: 1.0.2 + path-to-regexp: 3.3.0 + range-parser: 1.2.0 + + serve@14.2.6: + dependencies: + '@zeit/schemas': 2.36.0 + ajv: 8.18.0 + arg: 5.0.2 + boxen: 7.0.0 + chalk: 5.0.1 + chalk-template: 0.4.0 + clipboardy: 3.0.0 + compression: 1.8.1 + is-port-reachable: 4.0.0 + serve-handler: 6.1.7 + update-check: 1.5.4 + transitivePeerDependencies: + - supports-color + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@3.0.7: {} + + simple-code-frame@1.3.0: + dependencies: + kolorist: 1.8.0 + + source-map-js@1.2.1: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + source-map@0.7.6: {} + + stable-hash-x@0.2.0: {} + + stack-trace@1.0.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + strip-final-newline@2.0.0: {} + + strip-json-comments@2.0.1: {} + + strip-json-comments@3.1.1: {} + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.17 + ts-interface-checker: 0.1.13 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + systemjs@6.15.1: {} + + tailwindcss@3.4.19: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.14 + postcss-import: 15.1.0(postcss@8.5.14) + postcss-js: 4.1.0(postcss@8.5.14) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.14) + postcss-nested: 6.2.0(postcss@8.5.14) + postcss-selector-parser: 6.1.2 + resolve: 1.22.12 + sucrase: 3.35.1 + transitivePeerDependencies: + - tsx + - yaml + + terser@5.47.1: + dependencies: + '@jridgewell/source-map': 0.3.11 + acorn: 8.16.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tlds@1.261.0: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + ts-api-utils@2.5.0(typescript@6.0.3): + dependencies: + typescript: 6.0.3 + + ts-interface-checker@0.1.13: {} + + tslib@2.8.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@2.19.0: {} + + typescript-eslint@8.59.3(eslint@9.39.4(jiti@1.21.7))(typescript@6.0.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.59.3(@typescript-eslint/parser@8.59.3(eslint@9.39.4(jiti@1.21.7))(typescript@6.0.3))(eslint@9.39.4(jiti@1.21.7))(typescript@6.0.3) + '@typescript-eslint/parser': 8.59.3(eslint@9.39.4(jiti@1.21.7))(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.59.3(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.3(eslint@9.39.4(jiti@1.21.7))(typescript@6.0.3) + eslint: 9.39.4(jiti@1.21.7) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + typescript@6.0.3: {} + + uint8arrays@5.1.1: + dependencies: + multiformats: 13.4.2 + + unicode-canonical-property-names-ecmascript@2.0.1: {} + + unicode-match-property-ecmascript@2.0.0: + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.1 + unicode-property-aliases-ecmascript: 2.2.0 + + unicode-match-property-value-ecmascript@2.2.1: {} + + unicode-property-aliases-ecmascript@2.2.0: {} + + unicode-segmenter@0.14.5: {} + + unrs-resolver@1.11.1: + dependencies: + napi-postinstall: 0.3.4 + optionalDependencies: + '@unrs/resolver-binding-android-arm-eabi': 1.11.1 + '@unrs/resolver-binding-android-arm64': 1.11.1 + '@unrs/resolver-binding-darwin-arm64': 1.11.1 + '@unrs/resolver-binding-darwin-x64': 1.11.1 + '@unrs/resolver-binding-freebsd-x64': 1.11.1 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.1 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.11.1 + '@unrs/resolver-binding-linux-arm64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-arm64-musl': 1.11.1 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-riscv64-musl': 1.11.1 + '@unrs/resolver-binding-linux-s390x-gnu': 1.11.1 + '@unrs/resolver-binding-linux-x64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-x64-musl': 1.11.1 + '@unrs/resolver-binding-wasm32-wasi': 1.11.1 + '@unrs/resolver-binding-win32-arm64-msvc': 1.11.1 + '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 + '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 + + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + update-check@1.5.4: + dependencies: + registry-auth-token: 3.3.2 + registry-url: 3.1.0 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + vary@1.1.2: {} + + vite-bundle-analyzer@1.3.8: {} + + vite-prerender-plugin@0.5.13(vite@8.0.16(jiti@1.21.7)(terser@5.47.1)): + dependencies: + kolorist: 1.8.0 + magic-string: 0.30.21 + node-html-parser: 6.1.13 + simple-code-frame: 1.3.0 + source-map: 0.7.6 + stack-trace: 1.0.0 + vite: 8.0.16(jiti@1.21.7)(terser@5.47.1) + + vite@8.0.16(jiti@1.21.7)(terser@5.47.1): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.15 + rolldown: 1.0.3 + tinyglobby: 0.2.17 + optionalDependencies: + fsevents: 2.3.3 + jiti: 1.21.7 + terser: 5.47.1 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + widest-line@4.0.1: + dependencies: + string-width: 5.1.2 + + word-wrap@1.2.5: {} + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + + yallist@3.1.1: {} + + yocto-queue@0.1.0: {} + + zimmerframe@1.1.4: {} + + zod@3.25.76: {} diff --git a/bskyembed/pnpm-workspace.yaml b/bskyembed/pnpm-workspace.yaml new file mode 100644 index 0000000000..8d4e6ed30e --- /dev/null +++ b/bskyembed/pnpm-workspace.yaml @@ -0,0 +1,8 @@ +allowBuilds: + '@sentry/cli': true + 'core-js': true + 'esbuild': true + 'unrs-resolver': true +minimumReleaseAgeExclude: + - '@atproto/*' + - '@bsky.app/*' diff --git a/bskyembed/src/components/embed.tsx b/bskyembed/src/components/embed.tsx index 5dbde9426b..af7a97b8fe 100644 --- a/bskyembed/src/components/embed.tsx +++ b/bskyembed/src/components/embed.tsx @@ -1,5 +1,6 @@ import { AppBskyEmbedExternal, + AppBskyEmbedGallery, AppBskyEmbedImages, AppBskyEmbedRecord, AppBskyEmbedRecordWithMedia, @@ -43,6 +44,11 @@ export function Embed({ return } + // Case 1b: Gallery (Photos v2) + if (AppBskyEmbedGallery.isView(content)) { + return + } + // Case 2: External link if (AppBskyEmbedExternal.isView(content)) { return @@ -230,6 +236,8 @@ function Info({children}: {children: ComponentChildren}) { ) } +type GridImage = {thumb: string; alt: string} + function ImageEmbed({ content, labelInfo, @@ -240,20 +248,45 @@ function ImageEmbed({ if (labelInfo) { return {labelInfo} } + return ( + ({thumb: i.thumb, alt: i.alt}))} + /> + ) +} - switch (content.images.length) { +function GalleryEmbed({ + content, + labelInfo, +}: { + content: AppBskyEmbedGallery.View + labelInfo?: string +}) { + if (labelInfo) { + return {labelInfo} + } + const images = content.items + .filter(AppBskyEmbedGallery.isViewImage) + .map(i => ({thumb: i.thumbnail, alt: i.alt})) + return +} + +function ImageGrid({images}: {images: GridImage[]}) { + switch (images.length) { + case 0: + return null case 1: return ( {content.images[0].alt} ) case 2: return (
- {content.images.map((image, i) => ( + {images.map((image, i) => (
{content.images[0].alt}
- {content.images.slice(1).map((image, i) => ( + {images.slice(1).map((image, i) => (
) - case 4: + default: { + const remaining = images.length - 4 return (
- {content.images.map((image, i) => ( - {image.alt} - ))} + {images.slice(0, 4).map((image, i) => { + const isOverflowCell = i === 3 && remaining > 0 + return ( +
+ {image.alt} + {isOverflowCell && ( +
+ + +{remaining} + +
+ )} +
+ ) + })}
) - default: - return null + } } } @@ -416,6 +464,7 @@ function VideoEmbed({content}: {content: AppBskyEmbedVideo.View}) { controls playsinline preload="metadata" + // @ts-expect-error https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/video#loading loading="lazy" aria-label={content.alt || undefined} onClickCapture={evt => evt.stopPropagation()} diff --git a/bskyembed/src/components/post.tsx b/bskyembed/src/components/post.tsx index 4167dc0869..46faebc4a7 100644 --- a/bskyembed/src/components/post.tsx +++ b/bskyembed/src/components/post.tsx @@ -154,7 +154,9 @@ export function Post({thread}: Props) { } function PostContent({record}: {record: AppBskyFeedPost.Record | null}) { - if (!record) return null + // text-only check - posts with no text (e.g. gallery posts) would otherwise + // render an empty

that adds an extra flex gap above the embed + if (!record?.text) return null const rt = new RichText({ text: record.text, diff --git a/bskyembed/tsconfig.json b/bskyembed/tsconfig.json index 769488cc6e..1eead3556f 100644 --- a/bskyembed/tsconfig.json +++ b/bskyembed/tsconfig.json @@ -1,11 +1,10 @@ { "compilerOptions": { - "target": "ES5", - "lib": ["DOM", "DOM.Iterable", "ESNext"], + "target": "ES2015", + "lib": ["DOM", "ESNext"], "types": ["vite/client"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -17,7 +16,6 @@ "jsx": "react", "jsxFactory": "h", "jsxFragmentFactory": "Fragment", - "downlevelIteration": true }, - "include": ["src", "snippet", "vite.config.ts", "*.config.cjs", ".eslintrc.cjs"] + "include": ["src", "snippet", "vite.config.ts", "*.config.cjs", "eslint.config.mjs"] } diff --git a/bskyembed/tsconfig.snippet.json b/bskyembed/tsconfig.snippet.json index 163db7d30d..f8db18cc6f 100644 --- a/bskyembed/tsconfig.snippet.json +++ b/bskyembed/tsconfig.snippet.json @@ -1,8 +1,9 @@ { "compilerOptions": { - "target": "ES5", - "lib": ["DOM", "DOM.Iterable", "ESNext"], + "target": "ES2015", + "lib": ["DOM", "ESNext"], "strict": true, + "rootDir": "snippet", "outDir": "dist" }, "include": ["snippet"] diff --git a/bskyembed/vite.config.ts b/bskyembed/vite.config.ts index 7cd3ffce52..63e1f41027 100644 --- a/bskyembed/vite.config.ts +++ b/bskyembed/vite.config.ts @@ -1,10 +1,10 @@ import fs from 'node:fs' import {resolve} from 'node:path' -import preact from '@preact/preset-vite' +import {preact} from '@preact/preset-vite' import legacy from '@vitejs/plugin-legacy' import type {Plugin, UserConfig} from 'vite' -import paths from 'vite-tsconfig-paths' +import {analyzer} from 'vite-bundle-analyzer' /** * World's hackiest router, for dev only. Serves `/post.html` to requests that start with `/embed/` @@ -38,11 +38,13 @@ function devOnlyRouter(): Plugin { const config: UserConfig = { plugins: [ preact(), - paths(), legacy({ targets: ['defaults', 'not IE 11'], }), devOnlyRouter(), + analyzer({ + enabled: process.env.ANALYZE === '1', + }), ], build: { assetsDir: 'static', @@ -53,6 +55,9 @@ const config: UserConfig = { }, }, }, + resolve: { + tsconfigPaths: true, + }, } export default config diff --git a/bskyembed/yarn.lock b/bskyembed/yarn.lock deleted file mode 100644 index 9ee2c13fd0..0000000000 --- a/bskyembed/yarn.lock +++ /dev/null @@ -1,3516 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@aashutoshrathi/word-wrap@^1.2.3": - version "1.2.6" - resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" - integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== - -"@alloc/quick-lru@^5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" - integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== - -"@ampproject/remapping@^2.2.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" - integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.24" - -"@atproto/api@^0.15.25": - version "0.15.25" - resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.15.25.tgz#6ed0115a53b9890cd0067d6b0839f5223f18d2a2" - integrity sha512-dUqe920qyXd596AI5iJyBlSv0vK5fh1a8PC0zPoeM+FzGDiebnbxuJdPIZ3HeBT/xQ2ce9HwYk1Ih2G7a+JkzQ== - dependencies: - "@atproto/common-web" "^0.4.2" - "@atproto/lexicon" "^0.4.12" - "@atproto/syntax" "^0.4.0" - "@atproto/xrpc" "^0.7.1" - await-lock "^2.2.2" - multiformats "^9.9.0" - tlds "^1.234.0" - zod "^3.23.8" - -"@atproto/common-web@^0.4.2": - version "0.4.2" - resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.4.2.tgz#6e3add6939da93d3dfbc8f87e26dc4f57fad7259" - integrity sha512-vrXwGNoFGogodjQvJDxAeP3QbGtawgZute2ed1XdRO0wMixLk3qewtikZm06H259QDJVu6voKC5mubml+WgQUw== - dependencies: - graphemer "^1.4.0" - multiformats "^9.9.0" - uint8arrays "3.0.0" - zod "^3.23.8" - -"@atproto/lexicon@^0.4.12": - version "0.4.12" - resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.12.tgz#89a704789d983f8405a52095769b5b58d87f5af7" - integrity sha512-fcEvEQ1GpQYF5igZ4IZjPWEoWVpsEF22L9RexxLS3ptfySXLflEyH384e7HITzO/73McDeaJx3lqHIuqn9ulnw== - dependencies: - "@atproto/common-web" "^0.4.2" - "@atproto/syntax" "^0.4.0" - iso-datestring-validator "^2.2.2" - multiformats "^9.9.0" - zod "^3.23.8" - -"@atproto/syntax@^0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.4.0.tgz#bec71552087bb24c208a06ef418c0040b65542f2" - integrity sha512-b9y5ceHS8YKOfP3mdKmwAx5yVj9294UN7FG2XzP6V5aKUdFazEYRnR9m5n5ZQFKa3GNvz7de9guZCJ/sUTcOAA== - -"@atproto/xrpc@^0.7.1": - version "0.7.1" - resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.7.1.tgz#51a8fc131eb21bd1229129d0a46384accc50ad65" - integrity sha512-ANHEzlskYlMEdH18m+Itp3a8d0pEJao2qoDybDoMupTnoeNkya4VKIaOgAi6ERQnqatBBZyn9asW+7rJmSt/8g== - dependencies: - "@atproto/lexicon" "^0.4.12" - zod "^3.23.8" - -"@babel/code-frame@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" - integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== - dependencies: - "@babel/helper-validator-identifier" "^7.27.1" - js-tokens "^4.0.0" - picocolors "^1.1.1" - -"@babel/compat-data@^7.27.2", "@babel/compat-data@^7.27.7", "@babel/compat-data@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.0.tgz#9fc6fd58c2a6a15243cd13983224968392070790" - integrity sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw== - -"@babel/core@^7.22.1", "@babel/core@^7.27.4": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.0.tgz#55dad808d5bf3445a108eefc88ea3fdf034749a4" - integrity sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.28.0" - "@babel/helper-compilation-targets" "^7.27.2" - "@babel/helper-module-transforms" "^7.27.3" - "@babel/helpers" "^7.27.6" - "@babel/parser" "^7.28.0" - "@babel/template" "^7.27.2" - "@babel/traverse" "^7.28.0" - "@babel/types" "^7.28.0" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/generator@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.0.tgz#9cc2f7bd6eb054d77dc66c2664148a0c5118acd2" - integrity sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg== - dependencies: - "@babel/parser" "^7.28.0" - "@babel/types" "^7.28.0" - "@jridgewell/gen-mapping" "^0.3.12" - "@jridgewell/trace-mapping" "^0.3.28" - jsesc "^3.0.2" - -"@babel/helper-annotate-as-pure@^7.27.1", "@babel/helper-annotate-as-pure@^7.27.3": - version "7.27.3" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz#f31fd86b915fc4daf1f3ac6976c59be7084ed9c5" - integrity sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg== - dependencies: - "@babel/types" "^7.27.3" - -"@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.27.2": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" - integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== - dependencies: - "@babel/compat-data" "^7.27.2" - "@babel/helper-validator-option" "^7.27.1" - browserslist "^4.24.0" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-create-class-features-plugin@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz#5bee4262a6ea5ddc852d0806199eb17ca3de9281" - integrity sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A== - dependencies: - "@babel/helper-annotate-as-pure" "^7.27.1" - "@babel/helper-member-expression-to-functions" "^7.27.1" - "@babel/helper-optimise-call-expression" "^7.27.1" - "@babel/helper-replace-supers" "^7.27.1" - "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" - "@babel/traverse" "^7.27.1" - semver "^6.3.1" - -"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz#05b0882d97ba1d4d03519e4bce615d70afa18c53" - integrity sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.27.1" - regexpu-core "^6.2.0" - semver "^6.3.1" - -"@babel/helper-define-polyfill-provider@^0.6.5": - version "0.6.5" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz#742ccf1cb003c07b48859fc9fa2c1bbe40e5f753" - integrity sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg== - dependencies: - "@babel/helper-compilation-targets" "^7.27.2" - "@babel/helper-plugin-utils" "^7.27.1" - debug "^4.4.1" - lodash.debounce "^4.0.8" - resolve "^1.22.10" - -"@babel/helper-globals@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" - integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== - -"@babel/helper-member-expression-to-functions@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz#ea1211276be93e798ce19037da6f06fbb994fa44" - integrity sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA== - dependencies: - "@babel/traverse" "^7.27.1" - "@babel/types" "^7.27.1" - -"@babel/helper-module-imports@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" - integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== - dependencies: - "@babel/traverse" "^7.27.1" - "@babel/types" "^7.27.1" - -"@babel/helper-module-transforms@^7.27.1", "@babel/helper-module-transforms@^7.27.3": - version "7.27.3" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz#db0bbcfba5802f9ef7870705a7ef8788508ede02" - integrity sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg== - dependencies: - "@babel/helper-module-imports" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - "@babel/traverse" "^7.27.3" - -"@babel/helper-optimise-call-expression@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz#c65221b61a643f3e62705e5dd2b5f115e35f9200" - integrity sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw== - dependencies: - "@babel/types" "^7.27.1" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" - integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== - -"@babel/helper-remap-async-to-generator@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz#4601d5c7ce2eb2aea58328d43725523fcd362ce6" - integrity sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.27.1" - "@babel/helper-wrap-function" "^7.27.1" - "@babel/traverse" "^7.27.1" - -"@babel/helper-replace-supers@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz#b1ed2d634ce3bdb730e4b52de30f8cccfd692bc0" - integrity sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.27.1" - "@babel/helper-optimise-call-expression" "^7.27.1" - "@babel/traverse" "^7.27.1" - -"@babel/helper-skip-transparent-expression-wrappers@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz#62bb91b3abba8c7f1fec0252d9dbea11b3ee7a56" - integrity sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg== - dependencies: - "@babel/traverse" "^7.27.1" - "@babel/types" "^7.27.1" - -"@babel/helper-string-parser@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" - integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== - -"@babel/helper-validator-identifier@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" - integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== - -"@babel/helper-validator-option@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" - integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== - -"@babel/helper-wrap-function@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz#b88285009c31427af318d4fe37651cd62a142409" - integrity sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ== - dependencies: - "@babel/template" "^7.27.1" - "@babel/traverse" "^7.27.1" - "@babel/types" "^7.27.1" - -"@babel/helpers@^7.27.6": - version "7.27.6" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.27.6.tgz#6456fed15b2cb669d2d1fabe84b66b34991d812c" - integrity sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug== - dependencies: - "@babel/template" "^7.27.2" - "@babel/types" "^7.27.6" - -"@babel/parser@^7.27.2", "@babel/parser@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.0.tgz#979829fbab51a29e13901e5a80713dbcb840825e" - integrity sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g== - dependencies: - "@babel/types" "^7.28.0" - -"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz#61dd8a8e61f7eb568268d1b5f129da3eee364bf9" - integrity sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/traverse" "^7.27.1" - -"@babel/plugin-bugfix-safari-class-field-initializer-scope@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz#43f70a6d7efd52370eefbdf55ae03d91b293856d" - integrity sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz#beb623bd573b8b6f3047bd04c32506adc3e58a72" - integrity sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz#e134a5479eb2ba9c02714e8c1ebf1ec9076124fd" - integrity sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" - "@babel/plugin-transform-optional-chaining" "^7.27.1" - -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz#bb1c25af34d75115ce229a1de7fa44bf8f955670" - integrity sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/traverse" "^7.27.1" - -"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": - version "7.21.0-placeholder-for-preset-env.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" - integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== - -"@babel/plugin-syntax-import-assertions@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz#88894aefd2b03b5ee6ad1562a7c8e1587496aecd" - integrity sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-syntax-import-attributes@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz#34c017d54496f9b11b61474e7ea3dfd5563ffe07" - integrity sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-syntax-jsx@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz#2f9beb5eff30fa507c5532d107daac7b888fa34c" - integrity sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-syntax-unicode-sets-regex@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" - integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-arrow-functions@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz#6e2061067ba3ab0266d834a9f94811196f2aba9a" - integrity sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-async-generator-functions@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz#1276e6c7285ab2cd1eccb0bc7356b7a69ff842c2" - integrity sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/helper-remap-async-to-generator" "^7.27.1" - "@babel/traverse" "^7.28.0" - -"@babel/plugin-transform-async-to-generator@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz#9a93893b9379b39466c74474f55af03de78c66e7" - integrity sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA== - dependencies: - "@babel/helper-module-imports" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/helper-remap-async-to-generator" "^7.27.1" - -"@babel/plugin-transform-block-scoped-functions@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz#558a9d6e24cf72802dd3b62a4b51e0d62c0f57f9" - integrity sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-block-scoping@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.0.tgz#e7c50cbacc18034f210b93defa89638666099451" - integrity sha512-gKKnwjpdx5sER/wl0WN0efUBFzF/56YZO0RJrSYP4CljXnP31ByY7fol89AzomdlLNzI36AvOTmYHsnZTCkq8Q== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-class-properties@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz#dd40a6a370dfd49d32362ae206ddaf2bb082a925" - integrity sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-class-static-block@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz#7e920d5625b25bbccd3061aefbcc05805ed56ce4" - integrity sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-classes@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.0.tgz#12fa46cffc32a6e084011b650539e880add8a0f8" - integrity sha512-IjM1IoJNw72AZFlj33Cu8X0q2XK/6AaVC3jQu+cgQ5lThWD5ajnuUAml80dqRmOhmPkTH8uAwnpMu9Rvj0LTRA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.27.3" - "@babel/helper-compilation-targets" "^7.27.2" - "@babel/helper-globals" "^7.28.0" - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/helper-replace-supers" "^7.27.1" - "@babel/traverse" "^7.28.0" - -"@babel/plugin-transform-computed-properties@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz#81662e78bf5e734a97982c2b7f0a793288ef3caa" - integrity sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/template" "^7.27.1" - -"@babel/plugin-transform-destructuring@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz#0f156588f69c596089b7d5b06f5af83d9aa7f97a" - integrity sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/traverse" "^7.28.0" - -"@babel/plugin-transform-dotall-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz#aa6821de864c528b1fecf286f0a174e38e826f4d" - integrity sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-duplicate-keys@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz#f1fbf628ece18e12e7b32b175940e68358f546d1" - integrity sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz#5043854ca620a94149372e69030ff8cb6a9eb0ec" - integrity sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-dynamic-import@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz#4c78f35552ac0e06aa1f6e3c573d67695e8af5a4" - integrity sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-explicit-resource-management@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz#45be6211b778dbf4b9d54c4e8a2b42fa72e09a1a" - integrity sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/plugin-transform-destructuring" "^7.28.0" - -"@babel/plugin-transform-exponentiation-operator@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz#fc497b12d8277e559747f5a3ed868dd8064f83e1" - integrity sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-export-namespace-from@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz#71ca69d3471edd6daa711cf4dfc3400415df9c23" - integrity sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-for-of@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz#bc24f7080e9ff721b63a70ac7b2564ca15b6c40a" - integrity sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" - -"@babel/plugin-transform-function-name@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz#4d0bf307720e4dce6d7c30fcb1fd6ca77bdeb3a7" - integrity sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ== - dependencies: - "@babel/helper-compilation-targets" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/traverse" "^7.27.1" - -"@babel/plugin-transform-json-strings@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz#a2e0ce6ef256376bd527f290da023983527a4f4c" - integrity sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-literals@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz#baaefa4d10a1d4206f9dcdda50d7d5827bb70b24" - integrity sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-logical-assignment-operators@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz#890cb20e0270e0e5bebe3f025b434841c32d5baa" - integrity sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-member-expression-literals@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz#37b88ba594d852418e99536f5612f795f23aeaf9" - integrity sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-modules-amd@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz#a4145f9d87c2291fe2d05f994b65dba4e3e7196f" - integrity sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA== - dependencies: - "@babel/helper-module-transforms" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-modules-commonjs@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz#8e44ed37c2787ecc23bdc367f49977476614e832" - integrity sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw== - dependencies: - "@babel/helper-module-transforms" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-modules-systemjs@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz#00e05b61863070d0f3292a00126c16c0e024c4ed" - integrity sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA== - dependencies: - "@babel/helper-module-transforms" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - "@babel/traverse" "^7.27.1" - -"@babel/plugin-transform-modules-umd@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz#63f2cf4f6dc15debc12f694e44714863d34cd334" - integrity sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w== - dependencies: - "@babel/helper-module-transforms" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-named-capturing-groups-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz#f32b8f7818d8fc0cc46ee20a8ef75f071af976e1" - integrity sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-new-target@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz#259c43939728cad1706ac17351b7e6a7bea1abeb" - integrity sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-nullish-coalescing-operator@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz#4f9d3153bf6782d73dd42785a9d22d03197bc91d" - integrity sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-numeric-separator@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz#614e0b15cc800e5997dadd9bd6ea524ed6c819c6" - integrity sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-object-rest-spread@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.0.tgz#d23021857ffd7cd809f54d624299b8086402ed8d" - integrity sha512-9VNGikXxzu5eCiQjdE4IZn8sb9q7Xsk5EXLDBKUYg1e/Tve8/05+KJEtcxGxAgCY5t/BpKQM+JEL/yT4tvgiUA== - dependencies: - "@babel/helper-compilation-targets" "^7.27.2" - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/plugin-transform-destructuring" "^7.28.0" - "@babel/plugin-transform-parameters" "^7.27.7" - "@babel/traverse" "^7.28.0" - -"@babel/plugin-transform-object-super@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz#1c932cd27bf3874c43a5cac4f43ebf970c9871b5" - integrity sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/helper-replace-supers" "^7.27.1" - -"@babel/plugin-transform-optional-catch-binding@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz#84c7341ebde35ccd36b137e9e45866825072a30c" - integrity sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-optional-chaining@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz#874ce3c4f06b7780592e946026eb76a32830454f" - integrity sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" - -"@babel/plugin-transform-parameters@^7.27.7": - version "7.27.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz#1fd2febb7c74e7d21cf3b05f7aebc907940af53a" - integrity sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-private-methods@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz#fdacbab1c5ed81ec70dfdbb8b213d65da148b6af" - integrity sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-private-property-in-object@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz#4dbbef283b5b2f01a21e81e299f76e35f900fb11" - integrity sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.27.1" - "@babel/helper-create-class-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-property-literals@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz#07eafd618800591e88073a0af1b940d9a42c6424" - integrity sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-react-jsx-development@^7.22.5": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz#47ff95940e20a3a70e68ad3d4fcb657b647f6c98" - integrity sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q== - dependencies: - "@babel/plugin-transform-react-jsx" "^7.27.1" - -"@babel/plugin-transform-react-jsx@^7.22.15", "@babel/plugin-transform-react-jsx@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz#1023bc94b78b0a2d68c82b5e96aed573bcfb9db0" - integrity sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.27.1" - "@babel/helper-module-imports" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/plugin-syntax-jsx" "^7.27.1" - "@babel/types" "^7.27.1" - -"@babel/plugin-transform-regenerator@^7.28.0": - version "7.28.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.1.tgz#bde80603442ff4bb4e910bc8b35485295d556ab1" - integrity sha512-P0QiV/taaa3kXpLY+sXla5zec4E+4t4Aqc9ggHlfZ7a2cp8/x/Gv08jfwEtn9gnnYIMvHx6aoOZ8XJL8eU71Dg== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-regexp-modifiers@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz#df9ba5577c974e3f1449888b70b76169998a6d09" - integrity sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-reserved-words@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz#40fba4878ccbd1c56605a4479a3a891ac0274bb4" - integrity sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-shorthand-properties@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz#532abdacdec87bfee1e0ef8e2fcdee543fe32b90" - integrity sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-spread@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz#1a264d5fc12750918f50e3fe3e24e437178abb08" - integrity sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" - -"@babel/plugin-transform-sticky-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz#18984935d9d2296843a491d78a014939f7dcd280" - integrity sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-template-literals@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz#1a0eb35d8bb3e6efc06c9fd40eb0bcef548328b8" - integrity sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-typeof-symbol@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz#70e966bb492e03509cf37eafa6dcc3051f844369" - integrity sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-unicode-escapes@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz#3e3143f8438aef842de28816ece58780190cf806" - integrity sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-unicode-property-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz#bdfe2d3170c78c5691a3c3be934c8c0087525956" - integrity sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-unicode-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz#25948f5c395db15f609028e370667ed8bae9af97" - integrity sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-unicode-sets-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz#6ab706d10f801b5c72da8bb2548561fa04193cd1" - integrity sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/preset-env@^7.27.2": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.28.0.tgz#d23a6bc17b43227d11db77081a0779c706b5569c" - integrity sha512-VmaxeGOwuDqzLl5JUkIRM1X2Qu2uKGxHEQWh+cvvbl7JuJRgKGJSfsEF/bUaxFhJl/XAyxBe7q7qSuTbKFuCyg== - dependencies: - "@babel/compat-data" "^7.28.0" - "@babel/helper-compilation-targets" "^7.27.2" - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/helper-validator-option" "^7.27.1" - "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.27.1" - "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.27.1" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.27.1" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.27.1" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.27.1" - "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" - "@babel/plugin-syntax-import-assertions" "^7.27.1" - "@babel/plugin-syntax-import-attributes" "^7.27.1" - "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" - "@babel/plugin-transform-arrow-functions" "^7.27.1" - "@babel/plugin-transform-async-generator-functions" "^7.28.0" - "@babel/plugin-transform-async-to-generator" "^7.27.1" - "@babel/plugin-transform-block-scoped-functions" "^7.27.1" - "@babel/plugin-transform-block-scoping" "^7.28.0" - "@babel/plugin-transform-class-properties" "^7.27.1" - "@babel/plugin-transform-class-static-block" "^7.27.1" - "@babel/plugin-transform-classes" "^7.28.0" - "@babel/plugin-transform-computed-properties" "^7.27.1" - "@babel/plugin-transform-destructuring" "^7.28.0" - "@babel/plugin-transform-dotall-regex" "^7.27.1" - "@babel/plugin-transform-duplicate-keys" "^7.27.1" - "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.27.1" - "@babel/plugin-transform-dynamic-import" "^7.27.1" - "@babel/plugin-transform-explicit-resource-management" "^7.28.0" - "@babel/plugin-transform-exponentiation-operator" "^7.27.1" - "@babel/plugin-transform-export-namespace-from" "^7.27.1" - "@babel/plugin-transform-for-of" "^7.27.1" - "@babel/plugin-transform-function-name" "^7.27.1" - "@babel/plugin-transform-json-strings" "^7.27.1" - "@babel/plugin-transform-literals" "^7.27.1" - "@babel/plugin-transform-logical-assignment-operators" "^7.27.1" - "@babel/plugin-transform-member-expression-literals" "^7.27.1" - "@babel/plugin-transform-modules-amd" "^7.27.1" - "@babel/plugin-transform-modules-commonjs" "^7.27.1" - "@babel/plugin-transform-modules-systemjs" "^7.27.1" - "@babel/plugin-transform-modules-umd" "^7.27.1" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.27.1" - "@babel/plugin-transform-new-target" "^7.27.1" - "@babel/plugin-transform-nullish-coalescing-operator" "^7.27.1" - "@babel/plugin-transform-numeric-separator" "^7.27.1" - "@babel/plugin-transform-object-rest-spread" "^7.28.0" - "@babel/plugin-transform-object-super" "^7.27.1" - "@babel/plugin-transform-optional-catch-binding" "^7.27.1" - "@babel/plugin-transform-optional-chaining" "^7.27.1" - "@babel/plugin-transform-parameters" "^7.27.7" - "@babel/plugin-transform-private-methods" "^7.27.1" - "@babel/plugin-transform-private-property-in-object" "^7.27.1" - "@babel/plugin-transform-property-literals" "^7.27.1" - "@babel/plugin-transform-regenerator" "^7.28.0" - "@babel/plugin-transform-regexp-modifiers" "^7.27.1" - "@babel/plugin-transform-reserved-words" "^7.27.1" - "@babel/plugin-transform-shorthand-properties" "^7.27.1" - "@babel/plugin-transform-spread" "^7.27.1" - "@babel/plugin-transform-sticky-regex" "^7.27.1" - "@babel/plugin-transform-template-literals" "^7.27.1" - "@babel/plugin-transform-typeof-symbol" "^7.27.1" - "@babel/plugin-transform-unicode-escapes" "^7.27.1" - "@babel/plugin-transform-unicode-property-regex" "^7.27.1" - "@babel/plugin-transform-unicode-regex" "^7.27.1" - "@babel/plugin-transform-unicode-sets-regex" "^7.27.1" - "@babel/preset-modules" "0.1.6-no-external-plugins" - babel-plugin-polyfill-corejs2 "^0.4.14" - babel-plugin-polyfill-corejs3 "^0.13.0" - babel-plugin-polyfill-regenerator "^0.6.5" - core-js-compat "^3.43.0" - semver "^6.3.1" - -"@babel/preset-modules@0.1.6-no-external-plugins": - version "0.1.6-no-external-plugins" - resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a" - integrity sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/types" "^7.4.4" - esutils "^2.0.2" - -"@babel/template@^7.27.1", "@babel/template@^7.27.2": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" - integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== - dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/parser" "^7.27.2" - "@babel/types" "^7.27.1" - -"@babel/traverse@^7.27.1", "@babel/traverse@^7.27.3", "@babel/traverse@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.0.tgz#518aa113359b062042379e333db18380b537e34b" - integrity sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg== - dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.28.0" - "@babel/helper-globals" "^7.28.0" - "@babel/parser" "^7.28.0" - "@babel/template" "^7.27.2" - "@babel/types" "^7.28.0" - debug "^4.3.1" - -"@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.27.6", "@babel/types@^7.28.0", "@babel/types@^7.4.4": - version "7.28.1" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.1.tgz#2aaf3c10b31ba03a77ac84f52b3912a0edef4cf9" - integrity sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ== - dependencies: - "@babel/helper-string-parser" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - -"@esbuild/aix-ppc64@0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.6.tgz#164b19122e2ed54f85469df9dea98ddb01d5e79e" - integrity sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw== - -"@esbuild/android-arm64@0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.6.tgz#8f539e7def848f764f6432598e51cc3820fde3a5" - integrity sha512-hd5zdUarsK6strW+3Wxi5qWws+rJhCCbMiC9QZyzoxfk5uHRIE8T287giQxzVpEvCwuJ9Qjg6bEjcRJcgfLqoA== - -"@esbuild/android-arm@0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.6.tgz#4ceb0f40113e9861169be83e2a670c260dd234ff" - integrity sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg== - -"@esbuild/android-x64@0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.6.tgz#ad4f280057622c25fe985c08999443a195dc63a8" - integrity sha512-0Z7KpHSr3VBIO9A/1wcT3NTy7EB4oNC4upJ5ye3R7taCc2GUdeynSLArnon5G8scPwaU866d3H4BCrE5xLW25A== - -"@esbuild/darwin-arm64@0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.6.tgz#d1f04027396b3d6afc96bacd0d13167dfd9f01f7" - integrity sha512-FFCssz3XBavjxcFxKsGy2DYK5VSvJqa6y5HXljKzhRZ87LvEi13brPrf/wdyl/BbpbMKJNOr1Sd0jtW4Ge1pAA== - -"@esbuild/darwin-x64@0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.6.tgz#2b4a6cedb799f635758d7832d75b23772c8ef68f" - integrity sha512-GfXs5kry/TkGM2vKqK2oyiLFygJRqKVhawu3+DOCk7OxLy/6jYkWXhlHwOoTb0WqGnWGAS7sooxbZowy+pK9Yg== - -"@esbuild/freebsd-arm64@0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.6.tgz#a26266cc97dd78dc3c3f3d6788b1b83697b1055d" - integrity sha512-aoLF2c3OvDn2XDTRvn8hN6DRzVVpDlj2B/F66clWd/FHLiHaG3aVZjxQX2DYphA5y/evbdGvC6Us13tvyt4pWg== - -"@esbuild/freebsd-x64@0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.6.tgz#9feb8e826735c568ebfd94859b22a3fbb6a9bdd2" - integrity sha512-2SkqTjTSo2dYi/jzFbU9Plt1vk0+nNg8YC8rOXXea+iA3hfNJWebKYPs3xnOUf9+ZWhKAaxnQNUf2X9LOpeiMQ== - -"@esbuild/linux-arm64@0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.6.tgz#c07cbed8e249f4c28e7f32781d36fc4695293d28" - integrity sha512-b967hU0gqKd9Drsh/UuAm21Khpoh6mPBSgz8mKRq4P5mVK8bpA+hQzmm/ZwGVULSNBzKdZPQBRT3+WuVavcWsQ== - -"@esbuild/linux-arm@0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.6.tgz#d6e2cd8ef3196468065d41f13fa2a61aaa72644a" - integrity sha512-SZHQlzvqv4Du5PrKE2faN0qlbsaW/3QQfUUc6yO2EjFcA83xnwm91UbEEVx4ApZ9Z5oG8Bxz4qPE+HFwtVcfyw== - -"@esbuild/linux-ia32@0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.6.tgz#3e682bd47c4eddcc4b8f1393dfc8222482f17997" - integrity sha512-aHWdQ2AAltRkLPOsKdi3xv0mZ8fUGPdlKEjIEhxCPm5yKEThcUjHpWB1idN74lfXGnZ5SULQSgtr5Qos5B0bPw== - -"@esbuild/linux-loong64@0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.6.tgz#473f5ea2e52399c08ad4cd6b12e6dbcddd630f05" - integrity sha512-VgKCsHdXRSQ7E1+QXGdRPlQ/e08bN6WMQb27/TMfV+vPjjTImuT9PmLXupRlC90S1JeNNW5lzkAEO/McKeJ2yg== - -"@esbuild/linux-mips64el@0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.6.tgz#9960631c9fd61605b0939c19043acf4ef2b51718" - integrity sha512-WViNlpivRKT9/py3kCmkHnn44GkGXVdXfdc4drNmRl15zVQ2+D2uFwdlGh6IuK5AAnGTo2qPB1Djppj+t78rzw== - -"@esbuild/linux-ppc64@0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.6.tgz#477cbf8bb04aa034b94f362c32c86b5c31db8d3e" - integrity sha512-wyYKZ9NTdmAMb5730I38lBqVu6cKl4ZfYXIs31Baf8aoOtB4xSGi3THmDYt4BTFHk7/EcVixkOV2uZfwU3Q2Jw== - -"@esbuild/linux-riscv64@0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.6.tgz#bcdb46c8fb8e93aa779e9a0a62cd4ac00dcac626" - integrity sha512-KZh7bAGGcrinEj4qzilJ4hqTY3Dg2U82c8bv+e1xqNqZCrCyc+TL9AUEn5WGKDzm3CfC5RODE/qc96OcbIe33w== - -"@esbuild/linux-s390x@0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.6.tgz#f412cf5fdf0aea849ff51c73fd817c6c0234d46d" - integrity sha512-9N1LsTwAuE9oj6lHMyyAM+ucxGiVnEqUdp4v7IaMmrwb06ZTEVCIs3oPPplVsnjPfyjmxwHxHMF8b6vzUVAUGw== - -"@esbuild/linux-x64@0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.6.tgz#d8233c09b5ebc0c855712dc5eeb835a3a3341108" - integrity sha512-A6bJB41b4lKFWRKNrWoP2LHsjVzNiaurf7wyj/XtFNTsnPuxwEBWHLty+ZE0dWBKuSK1fvKgrKaNjBS7qbFKig== - -"@esbuild/netbsd-arm64@0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.6.tgz#f51ae8dd1474172e73cf9cbaf8a38d1c72dd8f1a" - integrity sha512-IjA+DcwoVpjEvyxZddDqBY+uJ2Snc6duLpjmkXm/v4xuS3H+3FkLZlDm9ZsAbF9rsfP3zeA0/ArNDORZgrxR/Q== - -"@esbuild/netbsd-x64@0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.6.tgz#a267538602c0e50a858cf41dcfe5d8036f8da8e7" - integrity sha512-dUXuZr5WenIDlMHdMkvDc1FAu4xdWixTCRgP7RQLBOkkGgwuuzaGSYcOpW4jFxzpzL1ejb8yF620UxAqnBrR9g== - -"@esbuild/openbsd-arm64@0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.6.tgz#a51be60c425b85c216479b8c344ad0511635f2d2" - integrity sha512-l8ZCvXP0tbTJ3iaqdNf3pjaOSd5ex/e6/omLIQCVBLmHTlfXW3zAxQ4fnDmPLOB1x9xrcSi/xtCWFwCZRIaEwg== - -"@esbuild/openbsd-x64@0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.6.tgz#7e4a743c73f75562e29223ba69d0be6c9c9008da" - integrity sha512-hKrmDa0aOFOr71KQ/19JC7az1P0GWtCN1t2ahYAf4O007DHZt/dW8ym5+CUdJhQ/qkZmI1HAF8KkJbEFtCL7gw== - -"@esbuild/openharmony-arm64@0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.6.tgz#2087a5028f387879154ebf44bdedfafa17682e5b" - integrity sha512-+SqBcAWoB1fYKmpWoQP4pGtx+pUUC//RNYhFdbcSA16617cchuryuhOCRpPsjCblKukAckWsV+aQ3UKT/RMPcA== - -"@esbuild/sunos-x64@0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.6.tgz#56531f861723ea0dc6283a2bb8837304223cb736" - integrity sha512-dyCGxv1/Br7MiSC42qinGL8KkG4kX0pEsdb0+TKhmJZgCUDBGmyo1/ArCjNGiOLiIAgdbWgmWgib4HoCi5t7kA== - -"@esbuild/win32-arm64@0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.6.tgz#f4989f033deac6fae323acff58764fa8bc01436e" - integrity sha512-42QOgcZeZOvXfsCBJF5Afw73t4veOId//XD3i+/9gSkhSV6Gk3VPlWncctI+JcOyERv85FUo7RxuxGy+z8A43Q== - -"@esbuild/win32-ia32@0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.6.tgz#b260e9df71e3939eb33925076d39f63cec7d1525" - integrity sha512-4AWhgXmDuYN7rJI6ORB+uU9DHLq/erBbuMoAuB4VWJTu5KtCgcKYPynF0YI1VkBNuEfjNlLrFr9KZPJzrtLkrQ== - -"@esbuild/win32-x64@0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.6.tgz#4276edd5c105bc28b11c6a1f76fb9d29d1bd25c1" - integrity sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA== - -"@eslint-community/eslint-utils@^4.8.0", "@eslint-community/eslint-utils@^4.9.1": - version "4.9.1" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595" - integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== - dependencies: - eslint-visitor-keys "^3.4.3" - -"@eslint-community/regexpp@^4.12.1", "@eslint-community/regexpp@^4.12.2": - version "4.12.2" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" - integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== - -"@eslint/config-array@^0.21.1": - version "0.21.1" - resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.1.tgz#7d1b0060fea407f8301e932492ba8c18aff29713" - integrity sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA== - dependencies: - "@eslint/object-schema" "^2.1.7" - debug "^4.3.1" - minimatch "^3.1.2" - -"@eslint/config-helpers@^0.4.2": - version "0.4.2" - resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.4.2.tgz#1bd006ceeb7e2e55b2b773ab318d300e1a66aeda" - integrity sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw== - dependencies: - "@eslint/core" "^0.17.0" - -"@eslint/core@^0.17.0": - version "0.17.0" - resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.17.0.tgz#77225820413d9617509da9342190a2019e78761c" - integrity sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ== - dependencies: - "@types/json-schema" "^7.0.15" - -"@eslint/eslintrc@^3.3.1": - version "3.3.3" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.3.tgz#26393a0806501b5e2b6a43aa588a4d8df67880ac" - integrity sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ== - dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^10.0.1" - globals "^14.0.0" - ignore "^5.2.0" - import-fresh "^3.2.1" - js-yaml "^4.1.1" - minimatch "^3.1.2" - strip-json-comments "^3.1.1" - -"@eslint/js@9.39.2", "@eslint/js@^9.18.0": - version "9.39.2" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.39.2.tgz#2d4b8ec4c3ea13c1b3748e0c97ecd766bdd80599" - integrity sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA== - -"@eslint/object-schema@^2.1.7": - version "2.1.7" - resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.7.tgz#6e2126a1347e86a4dedf8706ec67ff8e107ebbad" - integrity sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA== - -"@eslint/plugin-kit@^0.4.1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz#9779e3fd9b7ee33571a57435cf4335a1794a6cb2" - integrity sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA== - dependencies: - "@eslint/core" "^0.17.0" - levn "^0.4.1" - -"@humanfs/core@^0.19.1": - version "0.19.1" - resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" - integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== - -"@humanfs/node@^0.16.6": - version "0.16.7" - resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.7.tgz#822cb7b3a12c5a240a24f621b5a2413e27a45f26" - integrity sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ== - dependencies: - "@humanfs/core" "^0.19.1" - "@humanwhocodes/retry" "^0.4.0" - -"@humanwhocodes/module-importer@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" - integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== - -"@humanwhocodes/retry@^0.4.0", "@humanwhocodes/retry@^0.4.2": - version "0.4.3" - resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" - integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== - -"@isaacs/cliui@^8.0.2": - version "8.0.2" - resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" - integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== - dependencies: - string-width "^5.1.2" - string-width-cjs "npm:string-width@^4.2.0" - strip-ansi "^7.0.1" - strip-ansi-cjs "npm:strip-ansi@^6.0.1" - wrap-ansi "^8.1.0" - wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" - -"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": - version "0.3.12" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz#2234ce26c62889f03db3d7fea43c1932ab3e927b" - integrity sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg== - dependencies: - "@jridgewell/sourcemap-codec" "^1.5.0" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/gen-mapping@^0.3.2": - version "0.3.5" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" - integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== - dependencies: - "@jridgewell/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/set-array@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" - integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== - -"@jridgewell/source-map@^0.3.3": - version "0.3.6" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a" - integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": - version "1.5.4" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz#7358043433b2e5da569aa02cbc4c121da3af27d7" - integrity sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw== - -"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.28": - version "0.3.29" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz#a58d31eaadaf92c6695680b2e1d464a9b8fbf7fc" - integrity sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@pkgjs/parseargs@^0.11.0": - version "0.11.0" - resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" - integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== - -"@preact/preset-vite@^2.10.2": - version "2.10.2" - resolved "https://registry.yarnpkg.com/@preact/preset-vite/-/preset-vite-2.10.2.tgz#7fdf94e523bab2588054858c3ecd4d253fdbcea9" - integrity sha512-K9wHlJOtkE+cGqlyQ5v9kL3Ge0Ql4LlIZjkUTL+1zf3nNdF88F9UZN6VTV8jdzBX9Fl7WSzeNMSDG7qECPmSmg== - dependencies: - "@babel/plugin-transform-react-jsx" "^7.22.15" - "@babel/plugin-transform-react-jsx-development" "^7.22.5" - "@prefresh/vite" "^2.4.1" - "@rollup/pluginutils" "^4.1.1" - babel-plugin-transform-hook-names "^1.0.2" - debug "^4.3.4" - picocolors "^1.1.1" - vite-prerender-plugin "^0.5.3" - -"@prefresh/babel-plugin@0.5.2": - version "0.5.2" - resolved "https://registry.yarnpkg.com/@prefresh/babel-plugin/-/babel-plugin-0.5.2.tgz#827a655a89bdf6efa5c7ae2cca3ed45c1a1b43db" - integrity sha512-AOl4HG6dAxWkJ5ndPHBgBa49oo/9bOiJuRDKHLSTyH+Fd9x00shTXpdiTj1W41l6oQIwUOAgJeHMn4QwIDpHkA== - -"@prefresh/core@^1.5.4": - version "1.5.5" - resolved "https://registry.yarnpkg.com/@prefresh/core/-/core-1.5.5.tgz#53af12227f8e16a77cac4d12ef83c66741cbb254" - integrity sha512-H6GTXUl4V4fe3ijz7yhSa/mZ+pGSOh7XaJb6uP/sQsagBx9yl0D1HKDaeoMQA8Ad2Xm27LqvbitMGSdY9UFSKQ== - -"@prefresh/utils@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@prefresh/utils/-/utils-1.2.1.tgz#c1cb3c3114839d07271a36b386bf03cf354d31d7" - integrity sha512-vq/sIuN5nYfYzvyayXI4C2QkprfNaHUQ9ZX+3xLD8nL3rWyzpxOm1+K7RtMbhd+66QcaISViK7amjnheQ/4WZw== - -"@prefresh/vite@^2.4.1": - version "2.4.8" - resolved "https://registry.yarnpkg.com/@prefresh/vite/-/vite-2.4.8.tgz#def4418ca57020b66fb49d407eb022b4552e4205" - integrity sha512-H7vlo9UbJInuRbZhRQrdgVqLP7qKjDoX7TgYWWwIVhEHeHO0hZ4zyicvwBrV1wX5A3EPOmArgRkUaN7cPI2VXQ== - dependencies: - "@babel/core" "^7.22.1" - "@prefresh/babel-plugin" "0.5.2" - "@prefresh/core" "^1.5.4" - "@prefresh/utils" "^1.2.1" - "@rollup/pluginutils" "^4.2.1" - -"@rollup/pluginutils@^4.1.1", "@rollup/pluginutils@^4.2.1": - version "4.2.1" - resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d" - integrity sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ== - dependencies: - estree-walker "^2.0.1" - picomatch "^2.2.2" - -"@rollup/rollup-android-arm-eabi@4.45.0": - version "4.45.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.0.tgz#0592252f7550bc0ea0474bb5a22430850f92bdbd" - integrity sha512-2o/FgACbji4tW1dzXOqAV15Eu7DdgbKsF2QKcxfG4xbh5iwU7yr5RRP5/U+0asQliSYv5M4o7BevlGIoSL0LXg== - -"@rollup/rollup-android-arm64@4.45.0": - version "4.45.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.0.tgz#00a51d1d4380cc677da80ac9da1a19e7806bf57e" - integrity sha512-PSZ0SvMOjEAxwZeTx32eI/j5xSYtDCRxGu5k9zvzoY77xUNssZM+WV6HYBLROpY5CkXsbQjvz40fBb7WPwDqtQ== - -"@rollup/rollup-darwin-arm64@4.45.0": - version "4.45.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.0.tgz#6638299dd282ebde1ebdf7dc5b0f150aa6e256e5" - integrity sha512-BA4yPIPssPB2aRAWzmqzQ3y2/KotkLyZukVB7j3psK/U3nVJdceo6qr9pLM2xN6iRP/wKfxEbOb1yrlZH6sYZg== - -"@rollup/rollup-darwin-x64@4.45.0": - version "4.45.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.0.tgz#33e61daa0a66890059648feda78e1075d4ea1bcb" - integrity sha512-Pr2o0lvTwsiG4HCr43Zy9xXrHspyMvsvEw4FwKYqhli4FuLE5FjcZzuQ4cfPe0iUFCvSQG6lACI0xj74FDZKRA== - -"@rollup/rollup-freebsd-arm64@4.45.0": - version "4.45.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.0.tgz#2cc4bd3ba7026cd5374e902285ce76e8fae0f6eb" - integrity sha512-lYE8LkE5h4a/+6VnnLiL14zWMPnx6wNbDG23GcYFpRW1V9hYWHAw9lBZ6ZUIrOaoK7NliF1sdwYGiVmziUF4vA== - -"@rollup/rollup-freebsd-x64@4.45.0": - version "4.45.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.0.tgz#64664ba3015deac473a5d6d6c60c068f274bf2d5" - integrity sha512-PVQWZK9sbzpvqC9Q0GlehNNSVHR+4m7+wET+7FgSnKG3ci5nAMgGmr9mGBXzAuE5SvguCKJ6mHL6vq1JaJ/gvw== - -"@rollup/rollup-linux-arm-gnueabihf@4.45.0": - version "4.45.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.0.tgz#7ab16acae3bcae863e9a9bc32038cd05e794a0ff" - integrity sha512-hLrmRl53prCcD+YXTfNvXd776HTxNh8wPAMllusQ+amcQmtgo3V5i/nkhPN6FakW+QVLoUUr2AsbtIRPFU3xIA== - -"@rollup/rollup-linux-arm-musleabihf@4.45.0": - version "4.45.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.0.tgz#bef91b1e924ab57e82e767dc2655264bbde7acc6" - integrity sha512-XBKGSYcrkdiRRjl+8XvrUR3AosXU0NvF7VuqMsm7s5nRy+nt58ZMB19Jdp1RdqewLcaYnpk8zeVs/4MlLZEJxw== - -"@rollup/rollup-linux-arm64-gnu@4.45.0": - version "4.45.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.0.tgz#0a811b16da334125f6e44570d0badf543876f49e" - integrity sha512-fRvZZPUiBz7NztBE/2QnCS5AtqLVhXmUOPj9IHlfGEXkapgImf4W9+FSkL8cWqoAjozyUzqFmSc4zh2ooaeF6g== - -"@rollup/rollup-linux-arm64-musl@4.45.0": - version "4.45.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.0.tgz#e8c166efe3cb963faaa924c7721eafbade63036f" - integrity sha512-Btv2WRZOcUGi8XU80XwIvzTg4U6+l6D0V6sZTrZx214nrwxw5nAi8hysaXj/mctyClWgesyuxbeLylCBNauimg== - -"@rollup/rollup-linux-loongarch64-gnu@4.45.0": - version "4.45.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.0.tgz#239feea00fa2a1e734bdff09b8d1c90def2abbf5" - integrity sha512-Li0emNnwtUZdLwHjQPBxn4VWztcrw/h7mgLyHiEI5Z0MhpeFGlzaiBHpSNVOMB/xucjXTTcO+dhv469Djr16KA== - -"@rollup/rollup-linux-powerpc64le-gnu@4.45.0": - version "4.45.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.0.tgz#1de2f926bddbf7d689a089277c1284ea6df4b6d1" - integrity sha512-sB8+pfkYx2kvpDCfd63d5ScYT0Fz1LO6jIb2zLZvmK9ob2D8DeVqrmBDE0iDK8KlBVmsTNzrjr3G1xV4eUZhSw== - -"@rollup/rollup-linux-riscv64-gnu@4.45.0": - version "4.45.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.0.tgz#28dbac643244e477a7b931feb9b475aa826f84c1" - integrity sha512-5GQ6PFhh7E6jQm70p1aW05G2cap5zMOvO0se5JMecHeAdj5ZhWEHbJ4hiKpfi1nnnEdTauDXxPgXae/mqjow9w== - -"@rollup/rollup-linux-riscv64-musl@4.45.0": - version "4.45.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.0.tgz#5d05eeaedadec3625cd50e3ca5d35ef6f96a4bf0" - integrity sha512-N/euLsBd1rekWcuduakTo/dJw6U6sBP3eUq+RXM9RNfPuWTvG2w/WObDkIvJ2KChy6oxZmOSC08Ak2OJA0UiAA== - -"@rollup/rollup-linux-s390x-gnu@4.45.0": - version "4.45.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.0.tgz#55b0790f499fb7adc14eb074c4e46aef92915813" - integrity sha512-2l9sA7d7QdikL0xQwNMO3xURBUNEWyHVHfAsHsUdq+E/pgLTUcCE+gih5PCdmyHmfTDeXUWVhqL0WZzg0nua3g== - -"@rollup/rollup-linux-x64-gnu@4.45.0": - version "4.45.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.0.tgz#e822632fe5b324b16bdc37149149c8c760b031fd" - integrity sha512-XZdD3fEEQcwG2KrJDdEQu7NrHonPxxaV0/w2HpvINBdcqebz1aL+0vM2WFJq4DeiAVT6F5SUQas65HY5JDqoPw== - -"@rollup/rollup-linux-x64-musl@4.45.0": - version "4.45.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.0.tgz#19a3602cb8fabd7eb3087f0a1e1e01adac31bbff" - integrity sha512-7ayfgvtmmWgKWBkCGg5+xTQ0r5V1owVm67zTrsEY1008L5ro7mCyGYORomARt/OquB9KY7LpxVBZes+oSniAAQ== - -"@rollup/rollup-win32-arm64-msvc@4.45.0": - version "4.45.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.0.tgz#42e08bf3ea4fc463fc9f199c4f0310a736f03eb1" - integrity sha512-B+IJgcBnE2bm93jEW5kHisqvPITs4ddLOROAcOc/diBgrEiQJJ6Qcjby75rFSmH5eMGrqJryUgJDhrfj942apQ== - -"@rollup/rollup-win32-ia32-msvc@4.45.0": - version "4.45.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.0.tgz#043d25557f59d7e28dfe38ee1f60ddcb95a08124" - integrity sha512-+CXwwG66g0/FpWOnP/v1HnrGVSOygK/osUbu3wPRy8ECXjoYKjRAyfxYpDQOfghC5qPJYLPH0oN4MCOjwgdMug== - -"@rollup/rollup-win32-x64-msvc@4.45.0": - version "4.45.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.0.tgz#0a7eecae41f463d6591c8fecd7a5c5087345ee36" - integrity sha512-SRf1cytG7wqcHVLrBc9VtPK4pU5wxiB/lNIkNmW2ApKXIg+RpqwHfsaEK+e7eH4A1BpI6BX/aBWXxZCIrJg3uA== - -"@types/estree@1.0.6", "@types/estree@1.0.8", "@types/estree@^1.0.6": - version "1.0.6" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" - integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== - -"@types/json-schema@^7.0.15": - version "7.0.15" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" - integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== - -"@typescript-eslint/eslint-plugin@8.52.0": - version "8.52.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.52.0.tgz#9a9f1d2ee974ed77a8b1bda94e77123f697ee8b4" - integrity sha512-okqtOgqu2qmZJ5iN4TWlgfF171dZmx2FzdOv2K/ixL2LZWDStL8+JgQerI2sa8eAEfoydG9+0V96m7V+P8yE1Q== - dependencies: - "@eslint-community/regexpp" "^4.12.2" - "@typescript-eslint/scope-manager" "8.52.0" - "@typescript-eslint/type-utils" "8.52.0" - "@typescript-eslint/utils" "8.52.0" - "@typescript-eslint/visitor-keys" "8.52.0" - ignore "^7.0.5" - natural-compare "^1.4.0" - ts-api-utils "^2.4.0" - -"@typescript-eslint/parser@8.52.0": - version "8.52.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.52.0.tgz#9fae9f5f13ebb1c8f31a50c34381bfd6bf96a05f" - integrity sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg== - dependencies: - "@typescript-eslint/scope-manager" "8.52.0" - "@typescript-eslint/types" "8.52.0" - "@typescript-eslint/typescript-estree" "8.52.0" - "@typescript-eslint/visitor-keys" "8.52.0" - debug "^4.4.3" - -"@typescript-eslint/project-service@8.52.0": - version "8.52.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.52.0.tgz#5fb4c16af4eda6d74c70cbc62f5d3f77b96e4cbe" - integrity sha512-xD0MfdSdEmeFa3OmVqonHi+Cciab96ls1UhIF/qX/O/gPu5KXD0bY9lu33jj04fjzrXHcuvjBcBC+D3SNSadaw== - dependencies: - "@typescript-eslint/tsconfig-utils" "^8.52.0" - "@typescript-eslint/types" "^8.52.0" - debug "^4.4.3" - -"@typescript-eslint/scope-manager@8.52.0": - version "8.52.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.52.0.tgz#9884ff690fad30380ccabfb08af1ac200af6b4e5" - integrity sha512-ixxqmmCcc1Nf8S0mS0TkJ/3LKcC8mruYJPOU6Ia2F/zUUR4pApW7LzrpU3JmtePbRUTes9bEqRc1Gg4iyRnDzA== - dependencies: - "@typescript-eslint/types" "8.52.0" - "@typescript-eslint/visitor-keys" "8.52.0" - -"@typescript-eslint/tsconfig-utils@8.52.0", "@typescript-eslint/tsconfig-utils@^8.52.0": - version "8.52.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.52.0.tgz#0296751c22ed05c83787a6eaec65ae221bd8b8ed" - integrity sha512-jl+8fzr/SdzdxWJznq5nvoI7qn2tNYV/ZBAEcaFMVXf+K6jmXvAFrgo/+5rxgnL152f//pDEAYAhhBAZGrVfwg== - -"@typescript-eslint/type-utils@8.52.0": - version "8.52.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.52.0.tgz#6e554113f8a074cf9b2faa818d2ebfccb867d6c5" - integrity sha512-JD3wKBRWglYRQkAtsyGz1AewDu3mTc7NtRjR/ceTyGoPqmdS5oCdx/oZMWD5Zuqmo6/MpsYs0wp6axNt88/2EQ== - dependencies: - "@typescript-eslint/types" "8.52.0" - "@typescript-eslint/typescript-estree" "8.52.0" - "@typescript-eslint/utils" "8.52.0" - debug "^4.4.3" - ts-api-utils "^2.4.0" - -"@typescript-eslint/types@8.52.0", "@typescript-eslint/types@^8.52.0": - version "8.52.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.52.0.tgz#1eb0a16b324824bc23b89d109a267c38c9213c4a" - integrity sha512-LWQV1V4q9V4cT4H5JCIx3481iIFxH1UkVk+ZkGGAV1ZGcjGI9IoFOfg3O6ywz8QqCDEp7Inlg6kovMofsNRaGg== - -"@typescript-eslint/typescript-estree@8.52.0": - version "8.52.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.52.0.tgz#2ad7721c671be2127951286cb7f44c4ce55b0591" - integrity sha512-XP3LClsCc0FsTK5/frGjolyADTh3QmsLp6nKd476xNI9CsSsLnmn4f0jrzNoAulmxlmNIpeXuHYeEQv61Q6qeQ== - dependencies: - "@typescript-eslint/project-service" "8.52.0" - "@typescript-eslint/tsconfig-utils" "8.52.0" - "@typescript-eslint/types" "8.52.0" - "@typescript-eslint/visitor-keys" "8.52.0" - debug "^4.4.3" - minimatch "^9.0.5" - semver "^7.7.3" - tinyglobby "^0.2.15" - ts-api-utils "^2.4.0" - -"@typescript-eslint/utils@8.52.0": - version "8.52.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.52.0.tgz#b249be8264899b80d996fa353b4b84da4662f962" - integrity sha512-wYndVMWkweqHpEpwPhwqE2lnD2DxC6WVLupU/DOt/0/v+/+iQbbzO3jOHjmBMnhu0DgLULvOaU4h4pwHYi2oRQ== - dependencies: - "@eslint-community/eslint-utils" "^4.9.1" - "@typescript-eslint/scope-manager" "8.52.0" - "@typescript-eslint/types" "8.52.0" - "@typescript-eslint/typescript-estree" "8.52.0" - -"@typescript-eslint/visitor-keys@8.52.0": - version "8.52.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.52.0.tgz#50361c48a6302676230fe498f80f6decce4bf673" - integrity sha512-ink3/Zofus34nmBsPjow63FP5M7IGff0RKAgqR6+CFpdk22M7aLwC9gOcLGYqr7MczLPzZVERW9hRog3O4n1sQ== - dependencies: - "@typescript-eslint/types" "8.52.0" - eslint-visitor-keys "^4.2.1" - -"@vitejs/plugin-legacy@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@vitejs/plugin-legacy/-/plugin-legacy-7.0.0.tgz#4c39082988d2566f7277311841b9db4eae580321" - integrity sha512-qevhyYFUeZXBd/bAZGwpBgyn4GGAYije9YPV8Jg07newPCZtFEIlFlzsQowPbm87iKekOIL/90wKn+hvGkjzkg== - dependencies: - "@babel/core" "^7.27.4" - "@babel/preset-env" "^7.27.2" - browserslist "^4.25.0" - browserslist-to-esbuild "^2.1.1" - core-js "^3.43.0" - magic-string "^0.30.17" - regenerator-runtime "^0.14.1" - systemjs "^6.15.1" - -"@zeit/schemas@2.36.0": - version "2.36.0" - resolved "https://registry.yarnpkg.com/@zeit/schemas/-/schemas-2.36.0.tgz#7a1b53f4091e18d0b404873ea3e3c83589c765f2" - integrity sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg== - -acorn-jsx@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - -acorn@^8.14.0, acorn@^8.15.0: - version "8.15.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" - integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== - -ajv@8.12.0: - version "8.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" - integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - -ajv@^6.12.4: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ansi-align@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" - integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== - dependencies: - string-width "^4.1.0" - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-regex@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" - integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" - integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== - -any-promise@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" - integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== - -anymatch@~3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -arch@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11" - integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== - -arg@5.0.2, arg@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" - integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -autoprefixer@^10.4.19: - version "10.4.21" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.21.tgz#77189468e7a8ad1d9a37fbc08efc9f480cf0a95d" - integrity sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ== - dependencies: - browserslist "^4.24.4" - caniuse-lite "^1.0.30001702" - fraction.js "^4.3.7" - normalize-range "^0.1.2" - picocolors "^1.1.1" - postcss-value-parser "^4.2.0" - -await-lock@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/await-lock/-/await-lock-2.2.2.tgz#a95a9b269bfd2f69d22b17a321686f551152bcef" - integrity sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw== - -babel-plugin-polyfill-corejs2@^0.4.14: - version "0.4.14" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz#8101b82b769c568835611542488d463395c2ef8f" - integrity sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg== - dependencies: - "@babel/compat-data" "^7.27.7" - "@babel/helper-define-polyfill-provider" "^0.6.5" - semver "^6.3.1" - -babel-plugin-polyfill-corejs3@^0.13.0: - version "0.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz#bb7f6aeef7addff17f7602a08a6d19a128c30164" - integrity sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.6.5" - core-js-compat "^3.43.0" - -babel-plugin-polyfill-regenerator@^0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz#32752e38ab6f6767b92650347bf26a31b16ae8c5" - integrity sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.6.5" - -babel-plugin-transform-hook-names@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-hook-names/-/babel-plugin-transform-hook-names-1.0.2.tgz#0d75c2d78e8bbcdb258241131562b9cf07f010f3" - integrity sha512-5gafyjyyBTTdX/tQQ0hRgu4AhNHG/hqWi0ZZmg2xvs2FgRkJXzDNKBZCyoYqgFkovfDrgM8OoKg8karoUvWeCw== - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -binary-extensions@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" - integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== - -boolbase@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== - -boxen@7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-7.0.0.tgz#9e5f8c26e716793fc96edcf7cf754cdf5e3fbf32" - integrity sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg== - dependencies: - ansi-align "^3.0.1" - camelcase "^7.0.0" - chalk "^5.0.1" - cli-boxes "^3.0.0" - string-width "^5.1.2" - type-fest "^2.13.0" - widest-line "^4.0.1" - wrap-ansi "^8.0.1" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - -braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -browserslist-to-esbuild@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/browserslist-to-esbuild/-/browserslist-to-esbuild-2.1.1.tgz#50dc4c55a6889ba22c7b1bd820032f81b822faf0" - integrity sha512-KN+mty6C3e9AN8Z5dI1xeN15ExcRNeISoC3g7V0Kax/MMF9MSoYA2G7lkTTcVUFntiEjkpI0HNgqJC1NjdyNUw== - dependencies: - meow "^13.0.0" - -browserslist@^4.24.0, browserslist@^4.24.4, browserslist@^4.25.0, browserslist@^4.25.1: - version "4.25.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.25.1.tgz#ba9e8e6f298a1d86f829c9b975e07948967bb111" - integrity sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw== - dependencies: - caniuse-lite "^1.0.30001726" - electron-to-chromium "^1.5.173" - node-releases "^2.0.19" - update-browserslist-db "^1.1.3" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== - -bytes@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camelcase-css@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" - integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== - -camelcase@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-7.0.1.tgz#f02e50af9fd7782bc8b88a3558c32fd3a388f048" - integrity sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw== - -caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001726: - version "1.0.30001727" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz#22e9706422ad37aa50556af8c10e40e2d93a8b85" - integrity sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q== - -chalk-template@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/chalk-template/-/chalk-template-0.4.0.tgz#692c034d0ed62436b9062c1707fadcd0f753204b" - integrity sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg== - dependencies: - chalk "^4.1.2" - -chalk@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.0.1.tgz#ca57d71e82bb534a296df63bbacc4a1c22b2a4b6" - integrity sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w== - -chalk@^4.0.0, chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chalk@^5.0.1: - version "5.6.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.6.2.tgz#b1238b6e23ea337af71c7f8a295db5af0c158aea" - integrity sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA== - -chokidar@^3.5.3: - version "3.6.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" - integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -cli-boxes@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-3.0.0.tgz#71a10c716feeba005e4504f36329ef0b17cf3145" - integrity sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g== - -clipboardy@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-3.0.0.tgz#f3876247404d334c9ed01b6f269c11d09a5e3092" - integrity sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg== - dependencies: - arch "^2.2.0" - execa "^5.1.1" - is-wsl "^2.2.0" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -commander@^2.20.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -commander@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" - integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== - -compressible@~2.0.18: - version "2.0.18" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" - integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== - dependencies: - mime-db ">= 1.43.0 < 2" - -compression@1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.8.1.tgz#4a45d909ac16509195a9a28bd91094889c180d79" - integrity sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w== - dependencies: - bytes "3.1.2" - compressible "~2.0.18" - debug "2.6.9" - negotiator "~0.6.4" - on-headers "~1.1.0" - safe-buffer "5.2.1" - vary "~1.1.2" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -content-disposition@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" - integrity sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA== - -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - -core-js-compat@^3.43.0: - version "3.44.0" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.44.0.tgz#62b9165b97e4cbdb8bca16b14818e67428b4a0f8" - integrity sha512-JepmAj2zfl6ogy34qfWtcE7nHKAJnKsQFRn++scjVS2bZFllwptzw61BZcZFYBPpUznLfAvh0LGhxKppk04ClA== - dependencies: - browserslist "^4.25.1" - -core-js@^3.43.0: - version "3.44.0" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.44.0.tgz#db4fd4fa07933c1d6898c8b112a1119a9336e959" - integrity sha512-aFCtd4l6GvAXwVEh3XbbVqJGHDJt0OZRa+5ePGx3LLwi12WfexqQxcsohb2wgsa/92xtl19Hd66G/L+TaAxDMw== - -cross-spawn@^7.0.0: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -cross-spawn@^7.0.3, cross-spawn@^7.0.6: - version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" - integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -css-select@^5.1.0: - version "5.2.2" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.2.2.tgz#01b6e8d163637bb2dd6c982ca4ed65863682786e" - integrity sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw== - dependencies: - boolbase "^1.0.0" - css-what "^6.1.0" - domhandler "^5.0.2" - domutils "^3.0.1" - nth-check "^2.0.1" - -css-what@^6.1.0: - version "6.2.2" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.2.2.tgz#cdcc8f9b6977719fdfbd1de7aec24abf756b9dea" - integrity sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA== - -cssesc@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" - integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== - -debug@2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4, debug@^4.4.1: - version "4.4.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" - integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== - dependencies: - ms "^2.1.3" - -debug@^4.3.2: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -debug@^4.4.3: - version "4.4.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" - integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== - dependencies: - ms "^2.1.3" - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - -deep-is@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - -didyoumean@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" - integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== - -dlv@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" - integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== - -dom-serializer@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" - integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== - dependencies: - domelementtype "^2.3.0" - domhandler "^5.0.2" - entities "^4.2.0" - -domelementtype@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" - integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== - -domhandler@^5.0.2, domhandler@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" - integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== - dependencies: - domelementtype "^2.3.0" - -domutils@^3.0.1: - version "3.2.2" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.2.2.tgz#edbfe2b668b0c1d97c24baf0f1062b132221bc78" - integrity sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw== - dependencies: - dom-serializer "^2.0.0" - domelementtype "^2.3.0" - domhandler "^5.0.3" - -eastasianwidth@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" - integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== - -electron-to-chromium@^1.5.173: - version "1.5.182" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.182.tgz#4ab73104f893938acb3ab9c28d7bec170c116b3e" - integrity sha512-Lv65Btwv9W4J9pyODI6EWpdnhfvrve/us5h1WspW8B2Fb0366REPtY3hX7ounk1CkV/TBjWCEvCBBbYbmV0qCA== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -emoji-regex@^9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" - integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== - -entities@^4.2.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" - integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== - -esbuild@^0.25.0: - version "0.25.6" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.6.tgz#9b82a3db2fa131aec069ab040fd57ed0a880cdcd" - integrity sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg== - optionalDependencies: - "@esbuild/aix-ppc64" "0.25.6" - "@esbuild/android-arm" "0.25.6" - "@esbuild/android-arm64" "0.25.6" - "@esbuild/android-x64" "0.25.6" - "@esbuild/darwin-arm64" "0.25.6" - "@esbuild/darwin-x64" "0.25.6" - "@esbuild/freebsd-arm64" "0.25.6" - "@esbuild/freebsd-x64" "0.25.6" - "@esbuild/linux-arm" "0.25.6" - "@esbuild/linux-arm64" "0.25.6" - "@esbuild/linux-ia32" "0.25.6" - "@esbuild/linux-loong64" "0.25.6" - "@esbuild/linux-mips64el" "0.25.6" - "@esbuild/linux-ppc64" "0.25.6" - "@esbuild/linux-riscv64" "0.25.6" - "@esbuild/linux-s390x" "0.25.6" - "@esbuild/linux-x64" "0.25.6" - "@esbuild/netbsd-arm64" "0.25.6" - "@esbuild/netbsd-x64" "0.25.6" - "@esbuild/openbsd-arm64" "0.25.6" - "@esbuild/openbsd-x64" "0.25.6" - "@esbuild/openharmony-arm64" "0.25.6" - "@esbuild/sunos-x64" "0.25.6" - "@esbuild/win32-arm64" "0.25.6" - "@esbuild/win32-ia32" "0.25.6" - "@esbuild/win32-x64" "0.25.6" - -escalade@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -eslint-plugin-simple-import-sort@^12.1.1: - version "12.1.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-12.1.1.tgz#e64bfdaf91c5b98a298619aa634a9f7aa43b709e" - integrity sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA== - -eslint-scope@^8.4.0: - version "8.4.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82" - integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - -eslint-visitor-keys@^3.4.3: - version "3.4.3" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" - integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== - -eslint-visitor-keys@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" - integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== - -eslint@^9.18.0: - version "9.39.2" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.39.2.tgz#cb60e6d16ab234c0f8369a3fe7cc87967faf4b6c" - integrity sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw== - dependencies: - "@eslint-community/eslint-utils" "^4.8.0" - "@eslint-community/regexpp" "^4.12.1" - "@eslint/config-array" "^0.21.1" - "@eslint/config-helpers" "^0.4.2" - "@eslint/core" "^0.17.0" - "@eslint/eslintrc" "^3.3.1" - "@eslint/js" "9.39.2" - "@eslint/plugin-kit" "^0.4.1" - "@humanfs/node" "^0.16.6" - "@humanwhocodes/module-importer" "^1.0.1" - "@humanwhocodes/retry" "^0.4.2" - "@types/estree" "^1.0.6" - ajv "^6.12.4" - chalk "^4.0.0" - cross-spawn "^7.0.6" - debug "^4.3.2" - escape-string-regexp "^4.0.0" - eslint-scope "^8.4.0" - eslint-visitor-keys "^4.2.1" - espree "^10.4.0" - esquery "^1.5.0" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^8.0.0" - find-up "^5.0.0" - glob-parent "^6.0.2" - ignore "^5.2.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - json-stable-stringify-without-jsonify "^1.0.1" - lodash.merge "^4.6.2" - minimatch "^3.1.2" - natural-compare "^1.4.0" - optionator "^0.9.3" - -espree@^10.0.1, espree@^10.4.0: - version "10.4.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837" - integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== - dependencies: - acorn "^8.15.0" - acorn-jsx "^5.3.2" - eslint-visitor-keys "^4.2.1" - -esquery@^1.5.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.7.0.tgz#08d048f261f0ddedb5bae95f46809463d9c9496d" - integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^5.1.0, estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -estree-walker@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" - integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -execa@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-glob@^3.3.0: - version "3.3.2" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" - integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-levenshtein@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== - -fastq@^1.6.0: - version "1.17.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" - integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== - dependencies: - reusify "^1.0.4" - -fdir@^6.4.4, fdir@^6.4.6: - version "6.4.6" - resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.6.tgz#2b268c0232697063111bbf3f64810a2a741ba281" - integrity sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w== - -fdir@^6.5.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" - integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== - -file-entry-cache@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" - integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== - dependencies: - flat-cache "^4.0.0" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -flat-cache@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" - integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== - dependencies: - flatted "^3.2.9" - keyv "^4.5.4" - -flatted@^3.2.9: - version "3.3.1" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" - integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== - -foreground-child@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" - integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg== - dependencies: - cross-spawn "^7.0.0" - signal-exit "^4.0.1" - -fraction.js@^4.3.7: - version "4.3.7" - resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" - integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== - -fsevents@~2.3.2, fsevents@~2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -glob-parent@^5.1.2, glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-parent@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" - -glob@^10.3.10: - version "10.3.12" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.12.tgz#3a65c363c2e9998d220338e88a5f6ac97302960b" - integrity sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg== - dependencies: - foreground-child "^3.1.0" - jackspeak "^2.3.6" - minimatch "^9.0.1" - minipass "^7.0.4" - path-scurry "^1.10.2" - -globals@^14.0.0: - version "14.0.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" - integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== - -globals@^15.14.0: - version "15.15.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-15.15.0.tgz#7c4761299d41c32b075715a4ce1ede7897ff72a8" - integrity sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg== - -globrex@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" - integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== - -graphemer@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" - integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -hasown@^2.0.0, hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - -he@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -ignore@^5.2.0: - version "5.3.1" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" - integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== - -ignore@^7.0.5: - version "7.0.5" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.5.tgz#4cb5f6cd7d4c7ab0365738c7aea888baa6d7efd9" - integrity sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== - -import-fresh@^3.2.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - -ini@~1.3.0: - version "1.3.8" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-core-module@^2.13.0: - version "2.13.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" - integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== - dependencies: - hasown "^2.0.0" - -is-core-module@^2.16.0: - version "2.16.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" - integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== - dependencies: - hasown "^2.0.2" - -is-docker@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-port-reachable@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-port-reachable/-/is-port-reachable-4.0.0.tgz#dac044091ef15319c8ab2f34604d8794181f8c2d" - integrity sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig== - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -iso-datestring-validator@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz#2daa80d2900b7a954f9f731d42f96ee0c19a6895" - integrity sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA== - -jackspeak@^2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" - integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ== - dependencies: - "@isaacs/cliui" "^8.0.2" - optionalDependencies: - "@pkgjs/parseargs" "^0.11.0" - -jiti@^1.21.0: - version "1.21.0" - resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d" - integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" - integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== - dependencies: - argparse "^2.0.1" - -jsesc@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" - integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== - -jsesc@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" - integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== - -json-buffer@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" - integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== - -json5@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - -keyv@^4.5.4: - version "4.5.4" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" - integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== - dependencies: - json-buffer "3.0.1" - -kolorist@^1.6.0, kolorist@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/kolorist/-/kolorist-1.8.0.tgz#edddbbbc7894bc13302cdf740af6374d4a04743c" - integrity sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ== - -levn@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" - integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - dependencies: - prelude-ls "^1.2.1" - type-check "~0.4.0" - -lilconfig@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" - integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== - -lilconfig@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.1.tgz#9d8a246fa753106cfc205fd2d77042faca56e5e3" - integrity sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ== - -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lodash.debounce@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" - integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== - -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== - -lru-cache@^10.2.0: - version "10.2.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3" - integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q== - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -"magic-string@0.x >= 0.26.0", magic-string@^0.30.17: - version "0.30.17" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" - integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== - dependencies: - "@jridgewell/sourcemap-codec" "^1.5.0" - -meow@^13.0.0: - version "13.2.0" - resolved "https://registry.yarnpkg.com/meow/-/meow-13.2.0.tgz#6b7d63f913f984063b3cc261b6e8800c4cd3474f" - integrity sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA== - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -merge2@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -micromatch@^4.0.4, micromatch@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== - dependencies: - braces "^3.0.2" - picomatch "^2.3.1" - -"mime-db@>= 1.43.0 < 2": - version "1.54.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" - integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== - -mime-db@~1.33.0: - version "1.33.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" - integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ== - -mime-types@2.1.18: - version "2.1.18" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" - integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ== - dependencies: - mime-db "~1.33.0" - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -minimatch@3.1.2, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^9.0.1: - version "9.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51" - integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^9.0.5: - version "9.0.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" - integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== - dependencies: - brace-expansion "^2.0.1" - -minimist@^1.2.0: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.4: - version "7.0.4" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c" - integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -multiformats@^9.4.2, multiformats@^9.9.0: - version "9.9.0" - resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.9.0.tgz#c68354e7d21037a8f1f8833c8ccd68618e8f1d37" - integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg== - -mz@^2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" - integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== - dependencies: - any-promise "^1.0.0" - object-assign "^4.0.1" - thenify-all "^1.0.0" - -nanoid@^3.3.11: - version "3.3.11" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" - integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== - -nanoid@^3.3.7: - version "3.3.7" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" - integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== - -negotiator@~0.6.4: - version "0.6.4" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7" - integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w== - -node-html-parser@^6.1.12: - version "6.1.13" - resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-6.1.13.tgz#a1df799b83df5c6743fcd92740ba14682083b7e4" - integrity sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg== - dependencies: - css-select "^5.1.0" - he "1.2.0" - -node-releases@^2.0.19: - version "2.0.19" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" - integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -normalize-range@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" - integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== - -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -nth-check@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" - integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== - dependencies: - boolbase "^1.0.0" - -object-assign@^4.0.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -object-hash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" - integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== - -on-headers@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.1.0.tgz#59da4f91c45f5f989c6e4bcedc5a3b0aed70ff65" - integrity sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A== - -onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -optionator@^0.9.3: - version "0.9.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" - integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== - dependencies: - "@aashutoshrathi/word-wrap" "^1.2.3" - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-inside@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w== - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-scurry@^1.10.2: - version "1.10.2" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.2.tgz#8f6357eb1239d5fa1da8b9f70e9c080675458ba7" - integrity sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA== - dependencies: - lru-cache "^10.2.0" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - -path-to-regexp@3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-3.3.0.tgz#f7f31d32e8518c2660862b644414b6d5c63a611b" - integrity sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw== - -picocolors@^1.0.0, picocolors@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" - integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== - -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -picomatch@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab" - integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== - -picomatch@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" - integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== - -pify@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== - -pirates@^4.0.1: - version "4.0.6" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" - integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== - -postcss-import@^15.1.0: - version "15.1.0" - resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-15.1.0.tgz#41c64ed8cc0e23735a9698b3249ffdbf704adc70" - integrity sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew== - dependencies: - postcss-value-parser "^4.0.0" - read-cache "^1.0.0" - resolve "^1.1.7" - -postcss-js@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.1.tgz#61598186f3703bab052f1c4f7d805f3991bee9d2" - integrity sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw== - dependencies: - camelcase-css "^2.0.1" - -postcss-load-config@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-4.0.2.tgz#7159dcf626118d33e299f485d6afe4aff7c4a3e3" - integrity sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ== - dependencies: - lilconfig "^3.0.0" - yaml "^2.3.4" - -postcss-nested@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.0.1.tgz#f83dc9846ca16d2f4fa864f16e9d9f7d0961662c" - integrity sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ== - dependencies: - postcss-selector-parser "^6.0.11" - -postcss-selector-parser@^6.0.11: - version "6.0.16" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz#3b88b9f5c5abd989ef4e2fc9ec8eedd34b20fb04" - integrity sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw== - dependencies: - cssesc "^3.0.0" - util-deprecate "^1.0.2" - -postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" - integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== - -postcss@^8.4.23, postcss@^8.4.38: - version "8.4.38" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e" - integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== - dependencies: - nanoid "^3.3.7" - picocolors "^1.0.0" - source-map-js "^1.2.0" - -postcss@^8.5.6: - version "8.5.6" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c" - integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== - dependencies: - nanoid "^3.3.11" - picocolors "^1.1.1" - source-map-js "^1.2.1" - -preact@^10.4.8: - version "10.20.1" - resolved "https://registry.yarnpkg.com/preact/-/preact-10.20.1.tgz#1bc598ab630d8612978f7533da45809a8298542b" - integrity sha512-JIFjgFg9B2qnOoGiYMVBtrcFxHqn+dNXbq76bVmcaHYJFYR4lW67AOcXgAYQQTDYXDOg/kTZrKPNCdRgJ2UJmw== - -prelude-ls@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" - integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== - -punycode@^2.1.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" - integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -range-parser@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" - integrity sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A== - -rc@^1.0.1, rc@^1.1.6: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -read-cache@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" - integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA== - dependencies: - pify "^2.3.0" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -regenerate-unicode-properties@^10.2.0: - version "10.2.0" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz#626e39df8c372338ea9b8028d1f99dc3fd9c3db0" - integrity sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA== - dependencies: - regenerate "^1.4.2" - -regenerate@^1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" - integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== - -regenerator-runtime@^0.14.1: - version "0.14.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" - integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== - -regexpu-core@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-6.2.0.tgz#0e5190d79e542bf294955dccabae04d3c7d53826" - integrity sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA== - dependencies: - regenerate "^1.4.2" - regenerate-unicode-properties "^10.2.0" - regjsgen "^0.8.0" - regjsparser "^0.12.0" - unicode-match-property-ecmascript "^2.0.0" - unicode-match-property-value-ecmascript "^2.1.0" - -registry-auth-token@3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.2.tgz#851fd49038eecb586911115af845260eec983f20" - integrity sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ== - dependencies: - rc "^1.1.6" - safe-buffer "^5.0.1" - -registry-url@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942" - integrity sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA== - dependencies: - rc "^1.0.1" - -regjsgen@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.8.0.tgz#df23ff26e0c5b300a6470cad160a9d090c3a37ab" - integrity sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q== - -regjsparser@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.12.0.tgz#0e846df6c6530586429377de56e0475583b088dc" - integrity sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ== - dependencies: - jsesc "~3.0.2" - -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve@^1.1.7, resolve@^1.22.2: - version "1.22.8" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" - integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -resolve@^1.22.10: - version "1.22.10" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" - integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== - dependencies: - is-core-module "^2.16.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -rollup@^4.40.0: - version "4.45.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.45.0.tgz#92d1b164eca1c6f2cb399ae7a1a8ee78967b6e33" - integrity sha512-WLjEcJRIo7i3WDDgOIJqVI2d+lAC3EwvOGy+Xfq6hs+GQuAA4Di/H72xmXkOhrIWFg2PFYSKZYfH0f4vfKXN4A== - dependencies: - "@types/estree" "1.0.8" - optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.45.0" - "@rollup/rollup-android-arm64" "4.45.0" - "@rollup/rollup-darwin-arm64" "4.45.0" - "@rollup/rollup-darwin-x64" "4.45.0" - "@rollup/rollup-freebsd-arm64" "4.45.0" - "@rollup/rollup-freebsd-x64" "4.45.0" - "@rollup/rollup-linux-arm-gnueabihf" "4.45.0" - "@rollup/rollup-linux-arm-musleabihf" "4.45.0" - "@rollup/rollup-linux-arm64-gnu" "4.45.0" - "@rollup/rollup-linux-arm64-musl" "4.45.0" - "@rollup/rollup-linux-loongarch64-gnu" "4.45.0" - "@rollup/rollup-linux-powerpc64le-gnu" "4.45.0" - "@rollup/rollup-linux-riscv64-gnu" "4.45.0" - "@rollup/rollup-linux-riscv64-musl" "4.45.0" - "@rollup/rollup-linux-s390x-gnu" "4.45.0" - "@rollup/rollup-linux-x64-gnu" "4.45.0" - "@rollup/rollup-linux-x64-musl" "4.45.0" - "@rollup/rollup-win32-arm64-msvc" "4.45.0" - "@rollup/rollup-win32-ia32-msvc" "4.45.0" - "@rollup/rollup-win32-x64-msvc" "4.45.0" - fsevents "~2.3.2" - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -safe-buffer@5.2.1, safe-buffer@^5.0.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -semver@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.7.3: - version "7.7.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" - integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== - -serve-handler@6.1.6: - version "6.1.6" - resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.6.tgz#50803c1d3e947cd4a341d617f8209b22bd76cfa1" - integrity sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ== - dependencies: - bytes "3.0.0" - content-disposition "0.5.2" - mime-types "2.1.18" - minimatch "3.1.2" - path-is-inside "1.0.2" - path-to-regexp "3.3.0" - range-parser "1.2.0" - -serve@^14.2.5: - version "14.2.5" - resolved "https://registry.yarnpkg.com/serve/-/serve-14.2.5.tgz#569e333b99a484b3a6d25acce4a569c8c4f96373" - integrity sha512-Qn/qMkzCcMFVPb60E/hQy+iRLpiU8PamOfOSYoAHmmF+fFFmpPpqa6Oci2iWYpTdOUM3VF+TINud7CfbQnsZbA== - dependencies: - "@zeit/schemas" "2.36.0" - ajv "8.12.0" - arg "5.0.2" - boxen "7.0.0" - chalk "5.0.1" - chalk-template "0.4.0" - clipboardy "3.0.0" - compression "1.8.1" - is-port-reachable "4.0.0" - serve-handler "6.1.6" - update-check "1.5.4" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -signal-exit@^3.0.3: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -signal-exit@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" - integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== - -simple-code-frame@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/simple-code-frame/-/simple-code-frame-1.3.0.tgz#11dd319656a09445639b9c26a0fcb7102c0de00e" - integrity sha512-MB4pQmETUBlNs62BBeRjIFGeuy/x6gGKh7+eRUemn1rCFhqo7K+4slPqsyizCbcbYLnaYqaoZ2FWsZ/jN06D8w== - dependencies: - kolorist "^1.6.0" - -source-map-js@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" - integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== - -source-map-js@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" - integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== - -source-map-support@~0.5.20: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -source-map@^0.7.4: - version "0.7.4" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" - integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== - -stack-trace@^1.0.0-pre2: - version "1.0.0-pre2" - resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-1.0.0-pre2.tgz#46a83a79f1b287807e9aaafc6a5dd8bcde626f9c" - integrity sha512-2ztBJRek8IVofG9DBJqdy2N5kulaacX30Nz7xmkYF6ale9WBVmIy6mFBchvGX7Vx/MyjBhx+Rcxqrj+dbOnQ6A== - -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^5.0.1, string-width@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" - integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== - dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^9.2.2" - strip-ansi "^7.0.1" - -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^7.0.1: - version "7.1.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" - integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== - dependencies: - ansi-regex "^6.0.1" - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== - -sucrase@^3.32.0: - version "3.35.0" - resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263" - integrity sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA== - dependencies: - "@jridgewell/gen-mapping" "^0.3.2" - commander "^4.0.0" - glob "^10.3.10" - lines-and-columns "^1.1.6" - mz "^2.7.0" - pirates "^4.0.1" - ts-interface-checker "^0.1.9" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -systemjs@^6.15.1: - version "6.15.1" - resolved "https://registry.yarnpkg.com/systemjs/-/systemjs-6.15.1.tgz#74175b6810e27a79e1177d21db5f0e3057118cea" - integrity sha512-Nk8c4lXvMB98MtbmjX7JwJRgJOL8fluecYCfCeYBznwmpOs8Bf15hLM6z4z71EDAhQVrQrI+wt1aLWSXZq+hXA== - -tailwindcss@^3.4.3: - version "3.4.3" - resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.3.tgz#be48f5283df77dfced705451319a5dffb8621519" - integrity sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A== - dependencies: - "@alloc/quick-lru" "^5.2.0" - arg "^5.0.2" - chokidar "^3.5.3" - didyoumean "^1.2.2" - dlv "^1.1.3" - fast-glob "^3.3.0" - glob-parent "^6.0.2" - is-glob "^4.0.3" - jiti "^1.21.0" - lilconfig "^2.1.0" - micromatch "^4.0.5" - normalize-path "^3.0.0" - object-hash "^3.0.0" - picocolors "^1.0.0" - postcss "^8.4.23" - postcss-import "^15.1.0" - postcss-js "^4.0.1" - postcss-load-config "^4.0.1" - postcss-nested "^6.0.1" - postcss-selector-parser "^6.0.11" - resolve "^1.22.2" - sucrase "^3.32.0" - -terser@^5.43.1: - version "5.43.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.43.1.tgz#88387f4f9794ff1a29e7ad61fb2932e25b4fdb6d" - integrity sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg== - dependencies: - "@jridgewell/source-map" "^0.3.3" - acorn "^8.14.0" - commander "^2.20.0" - source-map-support "~0.5.20" - -thenify-all@^1.0.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" - integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== - dependencies: - thenify ">= 3.1.0 < 4" - -"thenify@>= 3.1.0 < 4": - version "3.3.1" - resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" - integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== - dependencies: - any-promise "^1.0.0" - -tinyglobby@^0.2.14: - version "0.2.14" - resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.14.tgz#5280b0cf3f972b050e74ae88406c0a6a58f4079d" - integrity sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ== - dependencies: - fdir "^6.4.4" - picomatch "^4.0.2" - -tinyglobby@^0.2.15: - version "0.2.15" - resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" - integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== - dependencies: - fdir "^6.5.0" - picomatch "^4.0.3" - -tlds@^1.234.0: - version "1.252.0" - resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.252.0.tgz#71d9617f4ef4cc7347843bee72428e71b8b0f419" - integrity sha512-GA16+8HXvqtfEnw/DTcwB0UU354QE1n3+wh08oFjr6Znl7ZLAeUgYzCcK+/CCrOyE0vnHR8/pu3XXG3vDijXpQ== - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -ts-api-utils@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.4.0.tgz#2690579f96d2790253bdcf1ca35d569ad78f9ad8" - integrity sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA== - -ts-interface-checker@^0.1.9: - version "0.1.13" - resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" - integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== - -tsconfck@^3.0.3: - version "3.1.6" - resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-3.1.6.tgz#da1f0b10d82237ac23422374b3fce1edb23c3ead" - integrity sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w== - -type-check@^0.4.0, type-check@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" - integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== - dependencies: - prelude-ls "^1.2.1" - -type-fest@^2.13.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" - integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== - -typescript-eslint@^8.20.0: - version "8.52.0" - resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.52.0.tgz#b8c156b6f2b4dee202a85712ff6a37f614476413" - integrity sha512-atlQQJ2YkO4pfTVQmQ+wvYQwexPDOIgo+RaVcD7gHgzy/IQA+XTyuxNM9M9TVXvttkF7koBHmcwisKdOAf2EcA== - dependencies: - "@typescript-eslint/eslint-plugin" "8.52.0" - "@typescript-eslint/parser" "8.52.0" - "@typescript-eslint/typescript-estree" "8.52.0" - "@typescript-eslint/utils" "8.52.0" - -typescript@^5.8.3: - version "5.8.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" - integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== - -uint8arrays@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/uint8arrays/-/uint8arrays-3.0.0.tgz#260869efb8422418b6f04e3fac73a3908175c63b" - integrity sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA== - dependencies: - multiformats "^9.4.2" - -unicode-canonical-property-names-ecmascript@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz#cb3173fe47ca743e228216e4a3ddc4c84d628cc2" - integrity sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg== - -unicode-match-property-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" - integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== - dependencies: - unicode-canonical-property-names-ecmascript "^2.0.0" - unicode-property-aliases-ecmascript "^2.0.0" - -unicode-match-property-value-ecmascript@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz#a0401aee72714598f739b68b104e4fe3a0cb3c71" - integrity sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg== - -unicode-property-aliases-ecmascript@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" - integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== - -update-browserslist-db@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" - integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== - dependencies: - escalade "^3.2.0" - picocolors "^1.1.1" - -update-check@1.5.4: - version "1.5.4" - resolved "https://registry.yarnpkg.com/update-check/-/update-check-1.5.4.tgz#5b508e259558f1ad7dbc8b4b0457d4c9d28c8743" - integrity sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ== - dependencies: - registry-auth-token "3.3.2" - registry-url "3.1.0" - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -util-deprecate@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - -vite-prerender-plugin@^0.5.3: - version "0.5.11" - resolved "https://registry.yarnpkg.com/vite-prerender-plugin/-/vite-prerender-plugin-0.5.11.tgz#83e4f29e03269dceb763fb5ec2376dcc502aa79f" - integrity sha512-xWOhb8Ef2zoJIiinYVunIf3omRfUbEXcPEvrkQcrDpJ2yjDokxhvQ26eSJbkthRhymntWx6816jpATrJphh+ug== - dependencies: - kolorist "^1.8.0" - magic-string "0.x >= 0.26.0" - node-html-parser "^6.1.12" - simple-code-frame "^1.3.0" - source-map "^0.7.4" - stack-trace "^1.0.0-pre2" - -vite-tsconfig-paths@^5.1.4: - version "5.1.4" - resolved "https://registry.yarnpkg.com/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz#d9a71106a7ff2c1c840c6f1708042f76a9212ed4" - integrity sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w== - dependencies: - debug "^4.1.1" - globrex "^0.1.2" - tsconfck "^3.0.3" - -vite@^7.0.4: - version "7.0.4" - resolved "https://registry.yarnpkg.com/vite/-/vite-7.0.4.tgz#481204416277cfa7c93384c55984475c4276b18f" - integrity sha512-SkaSguuS7nnmV7mfJ8l81JGBFV7Gvzp8IzgE8A8t23+AxuNX61Q5H1Tpz5efduSN7NHC8nQXD3sKQKZAu5mNEA== - dependencies: - esbuild "^0.25.0" - fdir "^6.4.6" - picomatch "^4.0.2" - postcss "^8.5.6" - rollup "^4.40.0" - tinyglobby "^0.2.14" - optionalDependencies: - fsevents "~2.3.3" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -widest-line@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-4.0.1.tgz#a0fc673aaba1ea6f0a0d35b3c2795c9a9cc2ebf2" - integrity sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig== - dependencies: - string-width "^5.0.1" - -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" - integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== - dependencies: - ansi-styles "^6.1.0" - string-width "^5.0.1" - strip-ansi "^7.0.1" - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -yaml@^2.3.4: - version "2.4.1" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.1.tgz#2e57e0b5e995292c25c75d2658f0664765210eed" - integrity sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg== - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -zod@^3.23.8: - version "3.23.8" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" - integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== diff --git a/bskylink/package.json b/bskylink/package.json index d06247249d..23fb08aac2 100644 --- a/bskylink/package.json +++ b/bskylink/package.json @@ -4,7 +4,7 @@ "type": "module", "main": "index.ts", "scripts": { - "test": "npm run test:unit && npm run test:e2e", + "test": "yarn run test:unit && yarn run test:e2e", "test:e2e": "./tests/infra/with-test-db.sh node --loader ts-node/esm --test ./tests/index.ts", "test:unit": "node --loader ts-node/esm --test ./src/*.test.ts", "build": "tsc" diff --git a/bskyogcard/package.json b/bskyogcard/package.json index 5a2f02ca2a..b6c6342a5d 100644 --- a/bskyogcard/package.json +++ b/bskyogcard/package.json @@ -3,29 +3,38 @@ "version": "0.0.0", "type": "module", "main": "src/index.ts", + "devEngines": { + "packageManager": { + "name": "pnpm", + "version": "11.5.2", + "onFail": "warn" + } + }, "scripts": { - "start": "node --loader ts-node/esm ./src/bin.ts", - "dev": "node --watch-path ./src --loader ts-node/esm ./src/bin.ts", + "start": "tsx ./src/bin.ts", + "dev": "LOG_ENABLED=true LOG_LEVEL=debug node --watch-path ./src --import tsx ./src/bin.ts", "build": "tsc && cp -r src/assets dist/", - "install-fonts": "node --loader ts-node/esm scripts/install-fonts.ts" + "install-fonts": "tsx scripts/install-fonts.ts", + "typecheck": "tsc --noEmit" }, "dependencies": { - "@atproto/api": "0.12.19-next.0", - "@atproto/common": "^0.4.0", + "@atproto/api": "0.20.13", + "@atproto/common": "^0.6.1", "@resvg/resvg-js": "^2.6.2", "express": "^4.19.2", "express-prom-bundle": "^7.0.0", "http-terminator": "^3.2.0", "pino": "^9.2.0", "prom-client": "^15.1.3", - "react": "^18.3.1", - "satori": "^0.10.13", + "react": "^19.2.6", + "satori": "^0.26.0", "twemoji": "^14.0.2" }, "devDependencies": { "@types/express": "^4.17.21", - "@types/node": "^20.14.3", - "ts-node": "^10.9.2", - "typescript": "^5.4.5" + "@types/node": "^24.12.2", + "@types/react": "^19.2.15", + "tsx": "^4.21.0", + "typescript": "^6.0.3" } } diff --git a/bskyogcard/pnpm-lock.yaml b/bskyogcard/pnpm-lock.yaml new file mode 100644 index 0000000000..4c1b4452ee --- /dev/null +++ b/bskyogcard/pnpm-lock.yaml @@ -0,0 +1,2029 @@ +--- +lockfileVersion: '9.0' + +importers: + + .: + configDependencies: {} + packageManagerDependencies: + '@pnpm/exe': + specifier: 11.5.2 + version: 11.5.2 + pnpm: + specifier: 11.5.2 + version: 11.5.2 + +packages: + + '@pnpm/exe@11.5.2': + resolution: {integrity: sha512-4UFnP2rhNu1xjAQ+I1GdIUUEtCJuTYJlbpiWSFA4POAID3Lpt+2vrjImWO7eOJ7iCY3vpc4TFe2IW3sAolW4Kg==} + hasBin: true + + '@pnpm/linux-arm64@11.5.2': + resolution: {integrity: sha512-MbJySnu2y9cCBqlODLjUlZ87JnRC3Inq40rvGHWJSrSQ0PnuHeSw2NDMnLI8Hf9hCY+ooussRc5iiR4IAkjUvg==} + cpu: [arm64] + os: [linux] + + '@pnpm/linux-x64@11.5.2': + resolution: {integrity: sha512-g6g2BGpQA47wUACy6B1MdeSHPtnl6x4AeCg0IOWQ7xXorEtC+VRiSHhLpA5kByFGeSwyYh/nLc7mLul5DAaELw==} + cpu: [x64] + os: [linux] + + '@pnpm/linuxstatic-arm64@11.5.2': + resolution: {integrity: sha512-xTxs9BLxYW39BPNGnmvYCUBnMPWm4mzmzujmdYbpRxDnBXrx55qPR5K/3LSohX7VrmsdDrYxuH6AmG1AaOlIfA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@pnpm/linuxstatic-x64@11.5.2': + resolution: {integrity: sha512-RGmmc/SoGLD90gmOHcU85UEKNoNRstLvizli4wzDASmETz/VeqJOqU5nD1YBgjzcP72sUMS352dh4bmzTfKyvQ==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@pnpm/macos-arm64@11.5.2': + resolution: {integrity: sha512-gW3A2jRlC3SJRw8qX2SAzjMIu9o98daTSqCKzeeYcjF/uEbtbz3dn4HqYrYffBnenKbc4hsgZQmNOHAvUKIlSg==} + cpu: [arm64] + os: [darwin] + + '@pnpm/win-arm64@11.5.2': + resolution: {integrity: sha512-+VJCDoH/pRzLXBikwjvxgAnGfQufT8EALBX8cfSmrwD40JABUZvgPtjBjde7OwEoK/XwtlH8w+ZceFV0K3/YHQ==} + cpu: [arm64] + os: [win32] + + '@pnpm/win-x64@11.5.2': + resolution: {integrity: sha512-zgglREh75RbFgV/E0tNRS03ElX+hJOV43KRSSeaboxtj3ei1rrguxOgOCXUs/GsizoHVsuD+qXGABE4Kc4GMCg==} + cpu: [x64] + os: [win32] + + '@reflink/reflink-darwin-arm64@0.1.19': + resolution: {integrity: sha512-ruy44Lpepdk1FqDz38vExBY/PVUsjxZA+chd9wozjUH9JjuDT/HEaQYA6wYN9mf041l0yLVar6BCZuWABJvHSA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@reflink/reflink-darwin-x64@0.1.19': + resolution: {integrity: sha512-By85MSWrMZa+c26TcnAy8SDk0sTUkYlNnwknSchkhHpGXOtjNDUOxJE9oByBnGbeuIE1PiQsxDG3Ud+IVV9yuA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@reflink/reflink-linux-arm64-gnu@0.1.19': + resolution: {integrity: sha512-7P+er8+rP9iNeN+bfmccM4hTAaLP6PQJPKWSA4iSk2bNvo6KU6RyPgYeHxXmzNKzPVRcypZQTpFgstHam6maVg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@reflink/reflink-linux-arm64-musl@0.1.19': + resolution: {integrity: sha512-37iO/Dp6m5DDaC2sf3zPtx/hl9FV3Xze4xoYidrxxS9bgP3S8ALroxRK6xBG/1TtfXKTvolvp+IjrUU6ujIGmA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@reflink/reflink-linux-x64-gnu@0.1.19': + resolution: {integrity: sha512-jbI8jvuYCaA3MVUdu8vLoLAFqC+iNMpiSuLbxlAgg7x3K5bsS8nOpTRnkLF7vISJ+rVR8W+7ThXlXlUQ93ulkw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@reflink/reflink-linux-x64-musl@0.1.19': + resolution: {integrity: sha512-e9FBWDe+lv7QKAwtKOt6A2W/fyy/aEEfr0g6j/hWzvQcrzHCsz07BNQYlNOjTfeytrtLU7k449H1PI95jA4OjQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@reflink/reflink-win32-arm64-msvc@0.1.19': + resolution: {integrity: sha512-09PxnVIQcd+UOn4WAW73WU6PXL7DwGS6wPlkMhMg2zlHHG65F3vHepOw06HFCq+N42qkaNAc8AKIabWvtk6cIQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@reflink/reflink-win32-x64-msvc@0.1.19': + resolution: {integrity: sha512-E//yT4ni2SyhwP8JRjVGWr3cbnhWDiPLgnQ66qqaanjjnMiu3O/2tjCPQXlcGc/DEYofpDc9fvhv6tALQsMV9w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@reflink/reflink@0.1.19': + resolution: {integrity: sha512-DmCG8GzysnCZ15bres3N5AHCmwBwYgp0As6xjhQ47rAUTUXxJiK+lLUxaGsX3hd/30qUpVElh05PbGuxRPgJwA==} + engines: {node: '>= 10'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + pnpm@11.5.2: + resolution: {integrity: sha512-ccYx44IGbvwlYl1c8CkHXeB7YbN/bic1D72Esb2lhkyMGWetwoB3a0XDCnFcA1mjvgj+9C1bsJ4rmQKZeWkpFg==} + engines: {node: '>=22.13'} + hasBin: true + +snapshots: + + '@pnpm/exe@11.5.2': + dependencies: + '@reflink/reflink': 0.1.19 + detect-libc: 2.1.2 + optionalDependencies: + '@pnpm/linux-arm64': 11.5.2 + '@pnpm/linux-x64': 11.5.2 + '@pnpm/linuxstatic-arm64': 11.5.2 + '@pnpm/linuxstatic-x64': 11.5.2 + '@pnpm/macos-arm64': 11.5.2 + '@pnpm/win-arm64': 11.5.2 + '@pnpm/win-x64': 11.5.2 + + '@pnpm/linux-arm64@11.5.2': + optional: true + + '@pnpm/linux-x64@11.5.2': + optional: true + + '@pnpm/linuxstatic-arm64@11.5.2': + optional: true + + '@pnpm/linuxstatic-x64@11.5.2': + optional: true + + '@pnpm/macos-arm64@11.5.2': + optional: true + + '@pnpm/win-arm64@11.5.2': + optional: true + + '@pnpm/win-x64@11.5.2': + optional: true + + '@reflink/reflink-darwin-arm64@0.1.19': + optional: true + + '@reflink/reflink-darwin-x64@0.1.19': + optional: true + + '@reflink/reflink-linux-arm64-gnu@0.1.19': + optional: true + + '@reflink/reflink-linux-arm64-musl@0.1.19': + optional: true + + '@reflink/reflink-linux-x64-gnu@0.1.19': + optional: true + + '@reflink/reflink-linux-x64-musl@0.1.19': + optional: true + + '@reflink/reflink-win32-arm64-msvc@0.1.19': + optional: true + + '@reflink/reflink-win32-x64-msvc@0.1.19': + optional: true + + '@reflink/reflink@0.1.19': + optionalDependencies: + '@reflink/reflink-darwin-arm64': 0.1.19 + '@reflink/reflink-darwin-x64': 0.1.19 + '@reflink/reflink-linux-arm64-gnu': 0.1.19 + '@reflink/reflink-linux-arm64-musl': 0.1.19 + '@reflink/reflink-linux-x64-gnu': 0.1.19 + '@reflink/reflink-linux-x64-musl': 0.1.19 + '@reflink/reflink-win32-arm64-msvc': 0.1.19 + '@reflink/reflink-win32-x64-msvc': 0.1.19 + + detect-libc@2.1.2: {} + + pnpm@11.5.2: {} + +--- +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@atproto/api': + specifier: 0.20.13 + version: 0.20.13 + '@atproto/common': + specifier: ^0.6.1 + version: 0.6.1 + '@resvg/resvg-js': + specifier: ^2.6.2 + version: 2.6.2 + express: + specifier: ^4.19.2 + version: 4.21.2 + express-prom-bundle: + specifier: ^7.0.0 + version: 7.0.2(prom-client@15.1.3) + http-terminator: + specifier: ^3.2.0 + version: 3.2.0 + pino: + specifier: ^9.2.0 + version: 9.2.0 + prom-client: + specifier: ^15.1.3 + version: 15.1.3 + react: + specifier: ^19.2.6 + version: 19.2.6 + satori: + specifier: ^0.26.0 + version: 0.26.0 + twemoji: + specifier: ^14.0.2 + version: 14.0.2 + devDependencies: + '@types/express': + specifier: ^4.17.21 + version: 4.17.23 + '@types/node': + specifier: ^24.12.2 + version: 24.12.2 + '@types/react': + specifier: ^19.2.15 + version: 19.2.15 + tsx: + specifier: ^4.21.0 + version: 4.21.0 + typescript: + specifier: ^6.0.3 + version: 6.0.3 + +packages: + + '@atproto/api@0.20.13': + resolution: {integrity: sha512-AN7NTUsygHXnXs5Cnc6v7/BAjQydwAhE0P8hL21RpIAB4fmTksRKTU3TV//wPg4fk9ESsfNwGIAlwoybbyoZKg==} + engines: {node: '>=22'} + + '@atproto/common-web@0.5.0': + resolution: {integrity: sha512-ReWnkuZdDU/74/I47gaI26uxQjHmpq4edp41NnZZQ5vIIKGb7Ei6pZHzDTUD9JURo109SKrPx9RMP2IQm0fOKA==} + engines: {node: '>=22'} + + '@atproto/common@0.6.1': + resolution: {integrity: sha512-mXR4R8nYFgs4gncFXNyIcxMcjuvpeCn3spu4pIlfWsri+MxXPr4jHWkjyv+b2JIvI+nns3L3ElFKWPEO4GlZdA==} + engines: {node: '>=22'} + + '@atproto/lex-cbor@0.1.0': + resolution: {integrity: sha512-SGL7BHPYN0OmvtnVixieYWyh2ghxI7iHADTLfz52Xp5zR+BZY3QggIKJ32KfyYN9Ss2tduaqsY5aclEki2Aq0A==} + engines: {node: '>=22'} + + '@atproto/lex-data@0.1.1': + resolution: {integrity: sha512-/xza8nU/YhtzhETnHL3QKKofaJ28/0NCzhT7LaYoUkm8EgypWp5ykEtmW52yLhQM2JF6fVa25g1soQmNTGqtSg==} + engines: {node: '>=22'} + + '@atproto/lex-json@0.1.0': + resolution: {integrity: sha512-oWUrRMwFyWpmi/5k1Se3xBTbP06XdxBS5iFuUz9LmqItaPXwrWRD87a9ldPvINQ/A2/mn7J6/qug8sDVlhD+vQ==} + engines: {node: '>=22'} + + '@atproto/lexicon@0.7.1': + resolution: {integrity: sha512-voNfNED5KUxn3vpo7N5DMRblBDfWf7kSfdKhJFC1RrLCxg38YbBzzURNVQJ32bp13Oot8kYfyXBWxTgtKLvw8w==} + engines: {node: '>=22'} + + '@atproto/syntax@0.6.1': + resolution: {integrity: sha512-kA4dQDoMPpWCH8N0Q4KoSq024u5MkVfDVa8DdhyLjGA72z/khbOf1jXKPv7NIL2oEc9aj7geKELdvqyf4ogopA==} + engines: {node: '>=22'} + + '@atproto/xrpc@0.8.0': + resolution: {integrity: sha512-NJy02bIKrWlE2NQkRV1kT0Cj0ixbuxlF/MejBdo4cPWAa9v3oZexvAcjjb0zaOYeABkaU14iyIhvn2G4e/oLpw==} + engines: {node: '>=22'} + + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + + '@resvg/resvg-js-android-arm-eabi@2.6.2': + resolution: {integrity: sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@resvg/resvg-js-android-arm64@2.6.2': + resolution: {integrity: sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@resvg/resvg-js-darwin-arm64@2.6.2': + resolution: {integrity: sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@resvg/resvg-js-darwin-x64@2.6.2': + resolution: {integrity: sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@resvg/resvg-js-linux-arm-gnueabihf@2.6.2': + resolution: {integrity: sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@resvg/resvg-js-linux-arm64-gnu@2.6.2': + resolution: {integrity: sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@resvg/resvg-js-linux-arm64-musl@2.6.2': + resolution: {integrity: sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@resvg/resvg-js-linux-x64-gnu@2.6.2': + resolution: {integrity: sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@resvg/resvg-js-linux-x64-musl@2.6.2': + resolution: {integrity: sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@resvg/resvg-js-win32-arm64-msvc@2.6.2': + resolution: {integrity: sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@resvg/resvg-js-win32-ia32-msvc@2.6.2': + resolution: {integrity: sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@resvg/resvg-js-win32-x64-msvc@2.6.2': + resolution: {integrity: sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@resvg/resvg-js@2.6.2': + resolution: {integrity: sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q==} + engines: {node: '>= 10'} + + '@shuding/opentype.js@1.4.0-beta.0': + resolution: {integrity: sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==} + engines: {node: '>= 8.0.0'} + hasBin: true + + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/express-serve-static-core@4.19.6': + resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} + + '@types/express@4.17.23': + resolution: {integrity: sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + + '@types/mime@1.3.5': + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + + '@types/node@24.12.2': + resolution: {integrity: sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==} + + '@types/qs@6.14.0': + resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/react@19.2.15': + resolution: {integrity: sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==} + + '@types/send@0.17.5': + resolution: {integrity: sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==} + + '@types/serve-static@1.15.8': + resolution: {integrity: sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==} + + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + + await-lock@3.0.0: + resolution: {integrity: sha512-eO6fLiSnrJrMdjWMNK8zbVRXPs2TKJg78iKZd9wDpN3na5tcoV6EoeiOlMgk2QaAQ1gIrK1YuMsJHXWqz89tSA==} + + base64-js@0.0.8: + resolution: {integrity: sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==} + engines: {node: '>= 0.4'} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + bintrees@1.0.2: + resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==} + + body-parser@1.20.3: + resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + boolean@3.2.0: + resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + camelize@1.0.1: + resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} + + cborg@4.5.8: + resolution: {integrity: sha512-6/viltD51JklRhq4L7jC3zgy6gryuG5xfZ3kzpE+PravtyeQLeQmCYLREhQH7pWENg5pY4Yu/XCd6a7dKScVlw==} + hasBin: true + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + cookie-signature@1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + + cookie@0.7.1: + resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} + engines: {node: '>= 0.6'} + + css-background-parser@0.1.0: + resolution: {integrity: sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==} + + css-box-shadow@1.0.0-3: + resolution: {integrity: sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==} + + css-color-keywords@1.0.0: + resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==} + engines: {node: '>=4'} + + css-gradient-parser@0.0.17: + resolution: {integrity: sha512-w2Xy9UMMwlKtou0vlRnXvWglPAceXCTtcmVSo8ZBUvqCV5aXEFP/PC6d+I464810I9FT++UACwTD5511bmGPUg==} + engines: {node: '>=16'} + + css-to-react-native@3.2.0: + resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + delay@5.0.0: + resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==} + engines: {node: '>=10'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + emoji-regex-xs@2.0.1: + resolution: {integrity: sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g==} + engines: {node: '>=10.0.0'} + + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + express-prom-bundle@7.0.2: + resolution: {integrity: sha512-ffFV4HGHvCKnkNJFqm42sYztRJE5mLgOj8MpGey1HOatuFhtcwXoBD2m5gca7ZbcyjkIf7lOH5ZdrhlrBf0sGw==} + engines: {node: '>=18'} + peerDependencies: + prom-client: '>=15.0.0' + + express@4.21.2: + resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} + engines: {node: '>= 0.10.0'} + + fast-printf@1.6.9: + resolution: {integrity: sha512-FChq8hbz65WMj4rstcQsFB0O7Cy++nmbNfLYnD9cYv2cRn8EG6k/MGn9kO/tjO66t09DLDugj3yL+V2o6Qftrg==} + engines: {node: '>=10.0'} + + fast-redact@3.5.0: + resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} + engines: {node: '>=6'} + + fflate@0.7.4: + resolution: {integrity: sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==} + + finalhandler@1.3.1: + resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} + engines: {node: '>= 0.8'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hex-rgb@4.3.0: + resolution: {integrity: sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==} + engines: {node: '>=6'} + + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + + http-terminator@3.2.0: + resolution: {integrity: sha512-JLjck1EzPaWjsmIf8bziM3p9fgR1Y3JoUKAkyYEbZmFrIvJM6I8vVJfBGWlEtV9IWOvzNnaTtjuwZeBY2kwB4g==} + engines: {node: '>=14'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + iso-datestring-validator@2.2.2: + resolution: {integrity: sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==} + + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + + jsonfile@5.0.0: + resolution: {integrity: sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==} + + linebreak@1.1.0: + resolution: {integrity: sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + multiformats@13.4.2: + resolution: {integrity: sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==} + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} + + p-timeout@3.2.0: + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} + engines: {node: '>=8'} + + p-wait-for@3.2.0: + resolution: {integrity: sha512-wpgERjNkLrBiFmkMEjuZJEWKKDrNfHCKA1OhyN1wg1FrLkULbviEy6py1AyJUgZ72YWFbZ38FIpnqvVqAlDUwA==} + engines: {node: '>=8'} + + pako@0.2.9: + resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} + + parse-css-color@0.2.1: + resolution: {integrity: sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-to-regexp@0.1.12: + resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} + + pino-abstract-transport@1.2.0: + resolution: {integrity: sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==} + + pino-std-serializers@6.2.2: + resolution: {integrity: sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==} + + pino-std-serializers@7.0.0: + resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} + + pino@8.21.0: + resolution: {integrity: sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==} + hasBin: true + + pino@9.2.0: + resolution: {integrity: sha512-g3/hpwfujK5a4oVbaefoJxezLzsDgLcNJeITvC6yrfwYeT9la+edCK42j5QpEQSQCZgTKapXvnQIdgZwvRaZug==} + hasBin: true + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + process-warning@3.0.0: + resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==} + + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + + prom-client@15.1.3: + resolution: {integrity: sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==} + engines: {node: ^16 || ^18 || >=20} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + engines: {node: '>=0.6'} + + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} + + react@19.2.6: + resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} + engines: {node: '>=0.10.0'} + + readable-stream@4.5.2: + resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + roarr@7.21.1: + resolution: {integrity: sha512-3niqt5bXFY1InKU8HKWqqYTYjtrBaxBMnXELXCXUYgtNYGUtZM5rB46HIC430AyacL95iEniGf7RgqsesykLmQ==} + engines: {node: '>=18.0'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-stable-stringify@2.4.3: + resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==} + engines: {node: '>=10'} + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + satori@0.26.0: + resolution: {integrity: sha512-tkMFrfIs3l2mQ2JEcyW0ADTy3zGggFRFzi6Ef8YozQSFsFKEqaSO1Y8F9wJg4//PJGQauMalHGTUEkPrFwhVPA==} + engines: {node: '>=16'} + + semver-compare@1.0.0: + resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} + + send@0.19.0: + resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} + engines: {node: '>= 0.8.0'} + + serve-static@1.16.2: + resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} + engines: {node: '>= 0.8.0'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + sonic-boom@3.8.1: + resolution: {integrity: sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==} + + sonic-boom@4.0.1: + resolution: {integrity: sha512-hTSD/6JMLyT4r9zeof6UtuBDpjJ9sO08/nmS5djaA9eozT9oOlNdpXSnzcgj4FTqpk3nkLrs61l4gip9r1HCrQ==} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + string.prototype.codepointat@0.2.1: + resolution: {integrity: sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + tdigest@0.1.2: + resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==} + + thread-stream@2.7.0: + resolution: {integrity: sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==} + + thread-stream@3.1.0: + resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + + tiny-inflate@1.0.3: + resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + + tlds@1.261.0: + resolution: {integrity: sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA==} + hasBin: true + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + + twemoji-parser@14.0.0: + resolution: {integrity: sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA==} + + twemoji@14.0.2: + resolution: {integrity: sha512-BzOoXIe1QVdmsUmZ54xbEH+8AgtOKUiG53zO5vVP2iUu6h5u9lN15NcuS6te4OY96qx0H7JK9vjjl9WQbkTRuA==} + + type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + uint8arrays@5.1.1: + resolution: {integrity: sha512-9muQwa4wZG4dKi9gMAIBtnk2Pw87SRpvWTH6lOGm19V2Uqxr4uomUf2PGqPnWc+qs06sN8owUU4jfcoWOcfwVQ==} + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + unicode-segmenter@0.14.5: + resolution: {integrity: sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g==} + + unicode-trie@2.0.0: + resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + url-value-parser@2.2.0: + resolution: {integrity: sha512-yIQdxJpgkPamPPAPuGdS7Q548rLhny42tg8d4vyTNzFqvOnwqrgHXvgehT09U7fwrzxi3RxCiXjoNUNnNOlQ8A==} + engines: {node: '>=6.0.0'} + + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + yoga-layout@3.2.1: + resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==} + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + +snapshots: + + '@atproto/api@0.20.13': + dependencies: + '@atproto/common-web': 0.5.0 + '@atproto/lexicon': 0.7.1 + '@atproto/syntax': 0.6.1 + '@atproto/xrpc': 0.8.0 + await-lock: 3.0.0 + multiformats: 13.4.2 + tlds: 1.261.0 + zod: 3.25.76 + + '@atproto/common-web@0.5.0': + dependencies: + '@atproto/lex-data': 0.1.1 + '@atproto/lex-json': 0.1.0 + '@atproto/syntax': 0.6.1 + zod: 3.25.76 + + '@atproto/common@0.6.1': + dependencies: + '@atproto/common-web': 0.5.0 + '@atproto/lex-cbor': 0.1.0 + '@atproto/lex-data': 0.1.1 + multiformats: 13.4.2 + pino: 8.21.0 + + '@atproto/lex-cbor@0.1.0': + dependencies: + '@atproto/lex-data': 0.1.1 + cborg: 4.5.8 + tslib: 2.8.1 + + '@atproto/lex-data@0.1.1': + dependencies: + multiformats: 13.4.2 + tslib: 2.8.1 + uint8arrays: 5.1.1 + unicode-segmenter: 0.14.5 + + '@atproto/lex-json@0.1.0': + dependencies: + '@atproto/lex-data': 0.1.1 + tslib: 2.8.1 + + '@atproto/lexicon@0.7.1': + dependencies: + '@atproto/common-web': 0.5.0 + '@atproto/syntax': 0.6.1 + multiformats: 13.4.2 + zod: 3.25.76 + + '@atproto/syntax@0.6.1': + dependencies: + iso-datestring-validator: 2.2.2 + tslib: 2.8.1 + + '@atproto/xrpc@0.8.0': + dependencies: + '@atproto/lexicon': 0.7.1 + zod: 3.25.76 + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@opentelemetry/api@1.9.0': {} + + '@resvg/resvg-js-android-arm-eabi@2.6.2': + optional: true + + '@resvg/resvg-js-android-arm64@2.6.2': + optional: true + + '@resvg/resvg-js-darwin-arm64@2.6.2': + optional: true + + '@resvg/resvg-js-darwin-x64@2.6.2': + optional: true + + '@resvg/resvg-js-linux-arm-gnueabihf@2.6.2': + optional: true + + '@resvg/resvg-js-linux-arm64-gnu@2.6.2': + optional: true + + '@resvg/resvg-js-linux-arm64-musl@2.6.2': + optional: true + + '@resvg/resvg-js-linux-x64-gnu@2.6.2': + optional: true + + '@resvg/resvg-js-linux-x64-musl@2.6.2': + optional: true + + '@resvg/resvg-js-win32-arm64-msvc@2.6.2': + optional: true + + '@resvg/resvg-js-win32-ia32-msvc@2.6.2': + optional: true + + '@resvg/resvg-js-win32-x64-msvc@2.6.2': + optional: true + + '@resvg/resvg-js@2.6.2': + optionalDependencies: + '@resvg/resvg-js-android-arm-eabi': 2.6.2 + '@resvg/resvg-js-android-arm64': 2.6.2 + '@resvg/resvg-js-darwin-arm64': 2.6.2 + '@resvg/resvg-js-darwin-x64': 2.6.2 + '@resvg/resvg-js-linux-arm-gnueabihf': 2.6.2 + '@resvg/resvg-js-linux-arm64-gnu': 2.6.2 + '@resvg/resvg-js-linux-arm64-musl': 2.6.2 + '@resvg/resvg-js-linux-x64-gnu': 2.6.2 + '@resvg/resvg-js-linux-x64-musl': 2.6.2 + '@resvg/resvg-js-win32-arm64-msvc': 2.6.2 + '@resvg/resvg-js-win32-ia32-msvc': 2.6.2 + '@resvg/resvg-js-win32-x64-msvc': 2.6.2 + + '@shuding/opentype.js@1.4.0-beta.0': + dependencies: + fflate: 0.7.4 + string.prototype.codepointat: 0.2.1 + + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 24.12.2 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 24.12.2 + + '@types/express-serve-static-core@4.19.6': + dependencies: + '@types/node': 24.12.2 + '@types/qs': 6.14.0 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.5 + + '@types/express@4.17.23': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 4.19.6 + '@types/qs': 6.14.0 + '@types/serve-static': 1.15.8 + + '@types/http-errors@2.0.5': {} + + '@types/mime@1.3.5': {} + + '@types/node@24.12.2': + dependencies: + undici-types: 7.16.0 + + '@types/qs@6.14.0': {} + + '@types/range-parser@1.2.7': {} + + '@types/react@19.2.15': + dependencies: + csstype: 3.2.3 + + '@types/send@0.17.5': + dependencies: + '@types/mime': 1.3.5 + '@types/node': 24.12.2 + + '@types/serve-static@1.15.8': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 24.12.2 + '@types/send': 0.17.5 + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + array-flatten@1.1.1: {} + + atomic-sleep@1.0.0: {} + + await-lock@3.0.0: {} + + base64-js@0.0.8: {} + + base64-js@1.5.1: {} + + bintrees@1.0.2: {} + + body-parser@1.20.3: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.13.0 + raw-body: 2.5.2 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + boolean@3.2.0: {} + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + camelize@1.0.1: {} + + cborg@4.5.8: {} + + color-name@1.1.4: {} + + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + cookie-signature@1.0.6: {} + + cookie@0.7.1: {} + + css-background-parser@0.1.0: {} + + css-box-shadow@1.0.0-3: {} + + css-color-keywords@1.0.0: {} + + css-gradient-parser@0.0.17: {} + + css-to-react-native@3.2.0: + dependencies: + camelize: 1.0.1 + css-color-keywords: 1.0.0 + postcss-value-parser: 4.2.0 + + csstype@3.2.3: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + delay@5.0.0: {} + + depd@2.0.0: {} + + destroy@1.2.0: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + emoji-regex-xs@2.0.1: {} + + encodeurl@1.0.2: {} + + encodeurl@2.0.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + escape-html@1.0.3: {} + + etag@1.8.1: {} + + event-target-shim@5.0.1: {} + + events@3.3.0: {} + + express-prom-bundle@7.0.2(prom-client@15.1.3): + dependencies: + '@types/express': 4.17.23 + express: 4.21.2 + on-finished: 2.4.1 + prom-client: 15.1.3 + url-value-parser: 2.2.0 + transitivePeerDependencies: + - supports-color + + express@4.21.2: + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.3 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.7.1 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.3.1 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.3 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.12 + proxy-addr: 2.0.7 + qs: 6.13.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.19.0 + serve-static: 1.16.2 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fast-printf@1.6.9: + dependencies: + boolean: 3.2.0 + + fast-redact@3.5.0: {} + + fflate@0.7.4: {} + + finalhandler@1.3.1: + dependencies: + debug: 2.6.9 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + forwarded@0.2.0: {} + + fresh@0.5.2: {} + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-tsconfig@4.14.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + has-symbols@1.1.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hex-rgb@4.3.0: {} + + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + http-terminator@3.2.0: + dependencies: + delay: 5.0.0 + p-wait-for: 3.2.0 + roarr: 7.21.1 + type-fest: 2.19.0 + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + inherits@2.0.4: {} + + ipaddr.js@1.9.1: {} + + iso-datestring-validator@2.2.2: {} + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + jsonfile@5.0.0: + dependencies: + universalify: 0.1.2 + optionalDependencies: + graceful-fs: 4.2.11 + + linebreak@1.1.0: + dependencies: + base64-js: 0.0.8 + unicode-trie: 2.0.0 + + math-intrinsics@1.1.0: {} + + media-typer@0.3.0: {} + + merge-descriptors@1.0.3: {} + + methods@1.1.2: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@1.6.0: {} + + ms@2.0.0: {} + + ms@2.1.3: {} + + multiformats@13.4.2: {} + + negotiator@0.6.3: {} + + object-inspect@1.13.4: {} + + on-exit-leak-free@2.1.2: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + p-finally@1.0.0: {} + + p-timeout@3.2.0: + dependencies: + p-finally: 1.0.0 + + p-wait-for@3.2.0: + dependencies: + p-timeout: 3.2.0 + + pako@0.2.9: {} + + parse-css-color@0.2.1: + dependencies: + color-name: 1.1.4 + hex-rgb: 4.3.0 + + parseurl@1.3.3: {} + + path-to-regexp@0.1.12: {} + + pino-abstract-transport@1.2.0: + dependencies: + readable-stream: 4.5.2 + split2: 4.2.0 + + pino-std-serializers@6.2.2: {} + + pino-std-serializers@7.0.0: {} + + pino@8.21.0: + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.5.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 1.2.0 + pino-std-serializers: 6.2.2 + process-warning: 3.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 3.8.1 + thread-stream: 2.7.0 + + pino@9.2.0: + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.5.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 1.2.0 + pino-std-serializers: 7.0.0 + process-warning: 3.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.4.3 + sonic-boom: 4.0.1 + thread-stream: 3.1.0 + + postcss-value-parser@4.2.0: {} + + process-warning@3.0.0: {} + + process@0.11.10: {} + + prom-client@15.1.3: + dependencies: + '@opentelemetry/api': 1.9.0 + tdigest: 0.1.2 + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + qs@6.13.0: + dependencies: + side-channel: 1.1.0 + + quick-format-unescaped@4.0.4: {} + + range-parser@1.2.1: {} + + raw-body@2.5.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + react@19.2.6: {} + + readable-stream@4.5.2: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + + real-require@0.2.0: {} + + resolve-pkg-maps@1.0.0: {} + + roarr@7.21.1: + dependencies: + fast-printf: 1.6.9 + safe-stable-stringify: 2.4.3 + semver-compare: 1.0.0 + + safe-buffer@5.2.1: {} + + safe-stable-stringify@2.4.3: {} + + safe-stable-stringify@2.5.0: {} + + safer-buffer@2.1.2: {} + + satori@0.26.0: + dependencies: + '@shuding/opentype.js': 1.4.0-beta.0 + css-background-parser: 0.1.0 + css-box-shadow: 1.0.0-3 + css-gradient-parser: 0.0.17 + css-to-react-native: 3.2.0 + emoji-regex-xs: 2.0.1 + escape-html: 1.0.3 + linebreak: 1.1.0 + parse-css-color: 0.2.1 + postcss-value-parser: 4.2.0 + yoga-layout: 3.2.1 + + semver-compare@1.0.0: {} + + send@0.19.0: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + + serve-static@1.16.2: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.0 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + sonic-boom@3.8.1: + dependencies: + atomic-sleep: 1.0.0 + + sonic-boom@4.0.1: + dependencies: + atomic-sleep: 1.0.0 + + split2@4.2.0: {} + + statuses@2.0.1: {} + + string.prototype.codepointat@0.2.1: {} + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + tdigest@0.1.2: + dependencies: + bintrees: 1.0.2 + + thread-stream@2.7.0: + dependencies: + real-require: 0.2.0 + + thread-stream@3.1.0: + dependencies: + real-require: 0.2.0 + + tiny-inflate@1.0.3: {} + + tlds@1.261.0: {} + + toidentifier@1.0.1: {} + + tslib@2.8.1: {} + + tsx@4.21.0: + dependencies: + esbuild: 0.27.7 + get-tsconfig: 4.14.0 + optionalDependencies: + fsevents: 2.3.3 + + twemoji-parser@14.0.0: {} + + twemoji@14.0.2: + dependencies: + fs-extra: 8.1.0 + jsonfile: 5.0.0 + twemoji-parser: 14.0.0 + universalify: 0.1.2 + + type-fest@2.19.0: {} + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + typescript@6.0.3: {} + + uint8arrays@5.1.1: + dependencies: + multiformats: 13.4.2 + + undici-types@7.16.0: {} + + unicode-segmenter@0.14.5: {} + + unicode-trie@2.0.0: + dependencies: + pako: 0.2.9 + tiny-inflate: 1.0.3 + + universalify@0.1.2: {} + + unpipe@1.0.0: {} + + url-value-parser@2.2.0: {} + + utils-merge@1.0.1: {} + + vary@1.1.2: {} + + yoga-layout@3.2.1: {} + + zod@3.25.76: {} diff --git a/bskyogcard/pnpm-workspace.yaml b/bskyogcard/pnpm-workspace.yaml new file mode 100644 index 0000000000..2d01407bb9 --- /dev/null +++ b/bskyogcard/pnpm-workspace.yaml @@ -0,0 +1,11 @@ +nodeLinker: 'hoisted' +autoInstallPeers: true # default: true +strictPeerDependencies: false # default: false +trustPolicy: 'no-downgrade' # default: off +trustPolicyIgnoreAfter: 10080 # 7 days, default: undefined +minimumReleaseAgeExclude: + - '@atproto/*' + - '@bsky.app/*' +allowBuilds: + cbor-extract: true + esbuild: true diff --git a/bskyogcard/src/assets/fonts/Inter-Bold.ttf b/bskyogcard/src/assets/fonts/Inter-Bold.ttf index fe23eeb9c9..9fb9b751e5 100644 Binary files a/bskyogcard/src/assets/fonts/Inter-Bold.ttf and b/bskyogcard/src/assets/fonts/Inter-Bold.ttf differ diff --git a/bskyogcard/src/assets/fonts/Inter-Regular.ttf b/bskyogcard/src/assets/fonts/Inter-Regular.ttf new file mode 100644 index 0000000000..b7aaca8de1 Binary files /dev/null and b/bskyogcard/src/assets/fonts/Inter-Regular.ttf differ diff --git a/bskyogcard/src/assets/fonts/Inter-SemiBold.ttf b/bskyogcard/src/assets/fonts/Inter-SemiBold.ttf new file mode 100644 index 0000000000..47f8ab1d68 Binary files /dev/null and b/bskyogcard/src/assets/fonts/Inter-SemiBold.ttf differ diff --git a/bskyogcard/src/bin.ts b/bskyogcard/src/bin.ts index ff550809db..b7bf261817 100644 --- a/bskyogcard/src/bin.ts +++ b/bskyogcard/src/bin.ts @@ -1,4 +1,4 @@ -import cluster, {Worker} from 'node:cluster' +import cluster, {type Worker} from 'node:cluster' import {envInt} from '@atproto/common' diff --git a/bskyogcard/src/components/AvatarBubbles.tsx b/bskyogcard/src/components/AvatarBubbles.tsx new file mode 100644 index 0000000000..ef7f2c1e89 --- /dev/null +++ b/bskyogcard/src/components/AvatarBubbles.tsx @@ -0,0 +1,121 @@ +import {Img} from './Img.js' + +const SCALE = 3 +const BASE_SIZE = 120 +const PADDING = 4 +const CONTENT_SCALE = (BASE_SIZE - PADDING * 2) / BASE_SIZE + +export const AVATAR_BUBBLES_SIZE = BASE_SIZE * SCALE + +type BubbleConfig = {size: number; x: number; y: number} + +const LAYOUTS: Record<1 | 2 | 3 | 4, BubbleConfig[]> = { + 1: [ + {size: 76, x: 0, y: 0}, + {size: 76, x: 44, y: 44}, + ], + 2: [ + {size: 76, x: 0, y: 0}, + {size: 76, x: 44, y: 44}, + ], + 3: [ + {size: 68, x: 0.5, y: 0}, + {size: 56, x: 40.5, y: 64}, + {size: 46, x: 73.5, y: 20}, + ], + 4: [ + {size: 68, x: 1, y: 1}, + {size: 56, x: 63, y: 52}, + {size: 42, x: 17, y: 77}, + {size: 32, x: 75, y: 12}, + ], +} + +const PERSON_PATH = + 'M12.233 2a4.433 4.433 0 1 0 0 8.867 4.433 4.433 0 0 0 0-8.867ZM12.233 12.133c-3.888 0-6.863 2.263-8.071 5.435-.346.906-.11 1.8.44 2.436.535.619 1.36.996 2.25.996h10.762c.89 0 1.716-.377 2.25-.996.55-.636.786-1.53.441-2.436-1.208-3.173-4.184-5.435-8.072-5.435Z' + +export function AvatarBubbles(props: {images: (Buffer | null)[]}) { + const count = Math.min(props.images.length, 4) as 1 | 2 | 3 | 4 + const layout = LAYOUTS[count] ?? LAYOUTS[2] + + const bubbles = layout.map((bubble, i) => ({ + ...bubble, + image: props.images[i] ?? null, + })) + + // For the 2-avi layout, render in reverse so the first (top-left) paints last (on top) + if (count <= 2) { + bubbles.reverse() + } + + return ( +

+ {bubbles.map((bubble, i) => ( + + ))} +
+ ) +} + +function AvatarBubble(props: { + image: Buffer | null + size: number + x: number + y: number +}) { + return ( +
+ {props.image ? ( + + ) : ( + + )} +
+ ) +} + +function Placeholder(props: {size: number}) { + const iconSize = props.size * 0.5 + const offset = (props.size - iconSize) / 2 + return ( +
+ + + +
+ ) +} diff --git a/bskyogcard/src/components/Butterfly.tsx b/bskyogcard/src/components/Butterfly.tsx index 5a4124975c..f84e816d1e 100644 --- a/bskyogcard/src/components/Butterfly.tsx +++ b/bskyogcard/src/components/Butterfly.tsx @@ -1,5 +1,3 @@ -import React from 'react' - export function Butterfly(props: React.SVGAttributes) { return ( | null +}) { + return ( +
+ +
+ {/* Avatar */} +
+ {ownerImage && } +
+ + {/* Spacer pushes the text block to the bottom-left */} +
+ + {/* Group Chat row */} +
+ + Group Chat +
+ + {/* Chat name */} +
+ {preview.name} +
+ + {/* By @handle */} +
+ {'By @' + preview.owner.handle} +
+
+
+ ) +} + +function ChatIcon(props: React.SVGProps) { + return ( + + + + + + + ) +} + +function Background(props: React.SVGProps) { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +} diff --git a/bskyogcard/src/components/Img.tsx b/bskyogcard/src/components/Img.tsx index cd63bc6a91..b5f56d76f1 100644 --- a/bskyogcard/src/components/Img.tsx +++ b/bskyogcard/src/components/Img.tsx @@ -1,5 +1,3 @@ -import React from 'react' - // @NOTE satori does not currently support webp, see vercel/satori#273 function detectMime(buf: Buffer): string { if (buf[0] === 0xff && buf[1] === 0xd8) return 'image/jpeg' diff --git a/bskyogcard/src/components/StarterPack.tsx b/bskyogcard/src/components/StarterPack.tsx index 29bb8f32ab..be4627f707 100644 --- a/bskyogcard/src/components/StarterPack.tsx +++ b/bskyogcard/src/components/StarterPack.tsx @@ -1,9 +1,9 @@ /* eslint-disable bsky-internal/avoid-unwrapped-text */ -import React from 'react' -import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' +import {type AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' import {Butterfly} from './Butterfly.js' import {Img} from './Img.js' +import * as bsky from '../types/bsky/index.js' export const STARTERPACK_HEIGHT = 630 export const STARTERPACK_WIDTH = 1200 @@ -18,7 +18,10 @@ export function StarterPack(props: { images: Map }) { const {starterPack, images} = props - const record = AppBskyGraphStarterpack.isRecord(starterPack.record) + const record = bsky.dangerousIsType( + starterPack.record, + AppBskyGraphStarterpack.isRecord, + ) ? starterPack.record : null const imagesArray = [...images.values()] @@ -54,6 +57,7 @@ export function StarterPack(props: { backgroundColor: 'black', color: 'white', fontFamily: 'Inter', + fontWeight: 700, }}> {/* image tiles */}
{record?.name || 'Starter Pack'}
diff --git a/bskyogcard/src/config.ts b/bskyogcard/src/config.ts index 306f6fe9e4..2921483e60 100644 --- a/bskyogcard/src/config.ts +++ b/bskyogcard/src/config.ts @@ -9,6 +9,7 @@ export type ServiceConfig = { metricsPort: number version?: string appviewUrl: string + chatUrl: string originVerify?: string } @@ -17,6 +18,7 @@ export type Environment = { metricsPort?: number version?: string appviewUrl?: string + chatUrl?: string originVerify?: string } @@ -26,6 +28,7 @@ export const readEnv = (): Environment => { metricsPort: envInt('CARD_METRICS_PORT'), version: envStr('CARD_VERSION'), appviewUrl: envStr('CARD_APPVIEW_URL'), + chatUrl: envStr('CARD_CHAT_URL'), originVerify: envStr('CARD_ORIGIN_VERIFY'), } } @@ -36,6 +39,7 @@ export const envToCfg = (env: Environment): Config => { metricsPort: env.metricsPort ?? 3001, version: env.version, appviewUrl: env.appviewUrl ?? 'https://api.bsky.app', + chatUrl: env.chatUrl ?? 'https://api.bsky.chat', originVerify: env.originVerify, } return { diff --git a/bskyogcard/src/context.ts b/bskyogcard/src/context.ts index 0c972c94de..9926b2f056 100644 --- a/bskyogcard/src/context.ts +++ b/bskyogcard/src/context.ts @@ -3,44 +3,72 @@ import * as path from 'node:path' import {fileURLToPath} from 'node:url' import {AtpAgent} from '@atproto/api' +import {type FontWeight} from 'satori' -import {Config} from './config.js' +import {type Config} from './config.js' const __DIRNAME = path.dirname(fileURLToPath(import.meta.url)) export type AppContextOptions = { cfg: Config appviewAgent: AtpAgent - fonts: {name: string; data: Buffer}[] + chatAgent: AtpAgent + fonts: {name: string; data: Buffer; weight?: FontWeight}[] } export class AppContext { cfg: Config appviewAgent: AtpAgent - fonts: {name: string; data: Buffer}[] + chatAgent: AtpAgent + fonts: {name: string; data: Buffer; weight?: FontWeight}[] abortController = new AbortController() - constructor(private opts: AppContextOptions) { - this.cfg = this.opts.cfg - this.appviewAgent = this.opts.appviewAgent - this.fonts = this.opts.fonts + private opts: AppContextOptions + + constructor(opts: AppContextOptions) { + this.opts = opts + this.cfg = opts.cfg + this.appviewAgent = opts.appviewAgent + this.chatAgent = opts.chatAgent + this.fonts = opts.fonts } static async fromConfig(cfg: Config, overrides?: Partial) { const appviewAgent = new AtpAgent({service: cfg.service.appviewUrl}) + const chatAgent = new AtpAgent({service: cfg.service.chatUrl}) const fontDirectory = path.join(__DIRNAME, 'assets', 'fonts') const fontFiles = readdirSync(fontDirectory) const fonts = fontFiles.map(file => { return { - name: path.basename(file, path.extname(file)), + name: getName(file), data: readFileSync(path.join(fontDirectory, file)), + weight: getWeight(file), } }) return new AppContext({ cfg, appviewAgent, + chatAgent, fonts, ...overrides, }) } } + +function getName(file: string): string { + const base = path.basename(file, path.extname(file)) + if (base.startsWith('Inter-')) return 'Inter' + return base +} + +function getWeight(file: string): FontWeight { + switch (path.basename(file, path.extname(file))) { + case 'Inter-Regular': + return 400 + case 'Inter-SemiBold': + return 600 + case 'Inter-Bold': + default: + return 700 + } +} diff --git a/bskyogcard/src/index.ts b/bskyogcard/src/index.ts index 53b5dbd6e1..c5bed89457 100644 --- a/bskyogcard/src/index.ts +++ b/bskyogcard/src/index.ts @@ -19,10 +19,13 @@ export class CardService { private terminator?: HttpTerminator private metricsTerminator?: HttpTerminator - constructor( - public app: express.Application, - public ctx: AppContext, - ) {} + public app: express.Application + public ctx: AppContext + + constructor(app: express.Application, ctx: AppContext) { + this.app = app + this.ctx = ctx + } static async create(cfg: Config): Promise { let app = express() diff --git a/bskyogcard/src/routes/avatar-bubbles.tsx b/bskyogcard/src/routes/avatar-bubbles.tsx new file mode 100644 index 0000000000..71085e038b --- /dev/null +++ b/bskyogcard/src/routes/avatar-bubbles.tsx @@ -0,0 +1,69 @@ +import resvg from '@resvg/resvg-js' +import {type Express} from 'express' +import satori from 'satori' + +import { + AVATAR_BUBBLES_SIZE, + AvatarBubbles, +} from '../components/AvatarBubbles.js' +import {type AppContext} from '../context.js' +import {httpLogger} from '../logger.js' +import { + getImage, + handler, + hideAvatarLabels, + originVerifyMiddleware, +} from './util.js' + +export default function (ctx: AppContext, app: Express) { + return app.get( + '/avatar-bubbles', + originVerifyMiddleware(ctx), + handler(async (req, res) => { + const didParam = req.query.did + const dids = (Array.isArray(didParam) ? didParam : [didParam]).filter( + (d): d is string => typeof d === 'string' && d.length > 0, + ) + if (dids.length < 1 || dids.length > 4) { + return res.status(400).end('did param must contain 1-4 DIDs') + } + + let profiles + try { + const result = await ctx.appviewAgent.api.app.bsky.actor.getProfiles({ + actors: dids, + }) + profiles = result.data.profiles + } catch (err) { + httpLogger.warn({err, dids}, 'could not fetch profiles') + return res.status(502).end('could not fetch profiles') + } + + const images = await Promise.all( + dids.map(async did => { + const profile = profiles.find(p => p.did === did) + if (!profile?.avatar) return null + if (profile.labels?.some(l => hideAvatarLabels.has(l.val))) + return null + try { + return await getImage(profile.avatar) + } catch (err) { + httpLogger.warn({err, did}, 'could not fetch avatar image') + return null + } + }), + ) + + const svg = await satori(, { + fonts: ctx.fonts, + height: AVATAR_BUBBLES_SIZE, + width: AVATAR_BUBBLES_SIZE, + }) + const output = await resvg.renderAsync(svg) + res.statusCode = 200 + res.setHeader('content-type', 'image/png') + res.setHeader('cdn-tag', dids.join(',')) + return res.end(output.asPng()) + }), + ) +} diff --git a/bskyogcard/src/routes/chat-invite.tsx b/bskyogcard/src/routes/chat-invite.tsx new file mode 100644 index 0000000000..36482743b8 --- /dev/null +++ b/bskyogcard/src/routes/chat-invite.tsx @@ -0,0 +1,82 @@ +import assert from 'node:assert' + +import {ChatBskyGroupDefs} from '@atproto/api' +import resvg from '@resvg/resvg-js' +import {type Express} from 'express' +import satori from 'satori' + +import { + CHAT_INVITE_HEIGHT, + CHAT_INVITE_WIDTH, + ChatInvite, +} from '../components/ChatInvite.js' +import {type AppContext} from '../context.js' +import {httpLogger} from '../logger.js' +import {loadEmojiAsSvg} from '../util.js' +import { + getImage, + handler, + hideAvatarLabels, + originVerifyMiddleware, +} from './util.js' + +export default function (ctx: AppContext, app: Express) { + return app.get( + '/chat-invite/:code', + originVerifyMiddleware(ctx), + handler(async (req, res) => { + const {code} = req.params + let preview: ChatBskyGroupDefs.JoinLinkPreviewView + try { + const result = await ctx.chatAgent.chat.bsky.group.getJoinLinkPreviews({ + codes: [code], + }) + const found = result.data.joinLinkPreviews[0] + if (!ChatBskyGroupDefs.isJoinLinkPreviewView(found)) { + return res.status(404).end('not found') + } + preview = found + } catch (err) { + httpLogger.warn({err, code}, 'could not fetch chat invite preview') + return res.status(404).end('not found') + } + + // Load the owner's avatar (if any, and not labeled). + let ownerImage: Buffer | null = null + const owner = preview.owner + const ownerHasSafeAvatar = + !!owner.avatar && !owner.labels?.some(l => hideAvatarLabels.has(l.val)) + if (ownerHasSafeAvatar) { + try { + assert(owner.avatar) + ownerImage = await getImage(owner.avatar) + } catch (err) { + httpLogger.warn( + {err, code, did: owner.did}, + 'could not fetch owner image', + ) + } + } + + const svg = await satori( + , + { + fonts: ctx.fonts, + height: CHAT_INVITE_HEIGHT, + width: CHAT_INVITE_WIDTH, + loadAdditionalAsset: async (lang, text) => { + if (lang === 'emoji') { + return (await loadEmojiAsSvg(text)) ?? '' + } + return '' + }, + }, + ) + const output = await resvg.renderAsync(svg) + res.statusCode = 200 + res.setHeader('content-type', 'image/png') + res.setHeader('cdn-tag', owner.did) + return res.end(output.asPng()) + }), + ) +} diff --git a/bskyogcard/src/routes/health.ts b/bskyogcard/src/routes/health.ts index 0cc69515eb..7486386348 100644 --- a/bskyogcard/src/routes/health.ts +++ b/bskyogcard/src/routes/health.ts @@ -1,6 +1,6 @@ -import {Express} from 'express' +import {type Express} from 'express' -import {AppContext} from '../context.js' +import {type AppContext} from '../context.js' import {handler} from './util.js' export default function (ctx: AppContext, app: Express) { diff --git a/bskyogcard/src/routes/index.ts b/bskyogcard/src/routes/index.ts index 0c40f89d3b..5c2a26e989 100644 --- a/bskyogcard/src/routes/index.ts +++ b/bskyogcard/src/routes/index.ts @@ -1,6 +1,8 @@ -import {Express} from 'express' +import {type Express} from 'express' -import {AppContext} from '../context.js' +import {type AppContext} from '../context.js' +import {default as avatarBubbles} from './avatar-bubbles.js' +import {default as chatInvite} from './chat-invite.js' import {default as health} from './health.js' import {default as starterPack} from './starter-pack.js' @@ -9,5 +11,7 @@ export * from './util.js' export default function (ctx: AppContext, app: Express) { app = health(ctx, app) // GET /_health app = starterPack(ctx, app) // GET /start/:actor/:rkey + app = chatInvite(ctx, app) // GET /chat-invite/:code + app = avatarBubbles(ctx, app) // GET /avatar-bubbles?dids=... return app } diff --git a/bskyogcard/src/routes/starter-pack.tsx b/bskyogcard/src/routes/starter-pack.tsx index 99a3e73fe5..51df50ca39 100644 --- a/bskyogcard/src/routes/starter-pack.tsx +++ b/bskyogcard/src/routes/starter-pack.tsx @@ -1,6 +1,5 @@ import assert from 'node:assert' -import React from 'react' import {type AppBskyGraphDefs, AtUri} from '@atproto/api' import resvg from '@resvg/resvg-js' import {type Express} from 'express' @@ -14,7 +13,13 @@ import { import {type AppContext} from '../context.js' import {httpLogger} from '../logger.js' import {loadEmojiAsSvg} from '../util.js' -import {handler, originVerifyMiddleware} from './util.js' +import { + getImage, + handler, + hideAvatarLabels, + originVerifyMiddleware, +} from './util.js' +import * as bsky from '../types/bsky/index.js' export default function (ctx: AppContext, app: Express) { return app.get( @@ -25,9 +30,9 @@ export default function (ctx: AppContext, app: Express) { const uri = AtUri.make(actor, 'app.bsky.graph.starterpack', rkey) let starterPack: AppBskyGraphDefs.StarterPackView try { - const result = await ctx.appviewAgent.api.app.bsky.graph.getStarterPack( - {starterPack: uri.toString()}, - ) + const result = await ctx.appviewAgent.app.bsky.graph.getStarterPack({ + starterPack: uri.toString(), + }) starterPack = result.data.starterPack } catch (err) { httpLogger.warn( @@ -37,12 +42,12 @@ export default function (ctx: AppContext, app: Express) { return res.status(404).end('not found') } const imageEntries = await Promise.all( - [starterPack.creator] - .concat(starterPack.listItemsSample.map(li => li.subject)) + ([starterPack.creator] as Array) + .concat((starterPack.listItemsSample ?? []).map(li => li.subject)) // has avatar .filter(p => p.avatar) // no sensitive labels - .filter(p => !p.labels.some(l => hideAvatarLabels.has(l.val))) + .filter(p => !p.labels?.some(l => hideAvatarLabels.has(l.val))) .map(async p => { try { assert(p.avatar) @@ -58,7 +63,12 @@ export default function (ctx: AppContext, app: Express) { }), ) const images = new Map( - imageEntries.filter(([_, image]) => image !== null).slice(0, 7), + imageEntries + .filter( + (entry): entry is readonly [string, Buffer] => + entry[1] !== null, + ) + .slice(0, 7), ) const svg = await satori( , @@ -68,8 +78,9 @@ export default function (ctx: AppContext, app: Express) { width: STARTERPACK_WIDTH, loadAdditionalAsset: async (code, text) => { if (code === 'emoji') { - return await loadEmojiAsSvg(text) + return (await loadEmojiAsSvg(text)) ?? '' } + return '' }, }, ) @@ -81,35 +92,3 @@ export default function (ctx: AppContext, app: Express) { }), ) } - -async function getImage(url: string) { - const response = await fetch(ensureJpeg(url)) - const arrayBuf = await response.arrayBuffer() // must drain body even if it will be discarded - if (response.status !== 200) return null - return Buffer.from(arrayBuf) -} - -// CDN URLs end with @jpeg, @webp, or no extension (which may default to webp). -// We want to ensure the image URLs we use are for jpegs, required for compat with satori. -function ensureJpeg(url: string) { - return url.replace(/(@[a-z]{3,5})?$/, '@jpeg') -} - -const hideAvatarLabels = new Set([ - '!hide', - '!warn', - 'porn', - 'sexual', - 'nudity', - 'sexual-figurative', - 'graphic-media', - 'gore', - 'self-harm', - 'sensitive', - 'security', - 'impersonation', - 'scam', - 'spam', - 'misleading', - 'inauthentic', -]) diff --git a/bskyogcard/src/routes/util.ts b/bskyogcard/src/routes/util.ts index 718ed592a1..9c441207b5 100644 --- a/bskyogcard/src/routes/util.ts +++ b/bskyogcard/src/routes/util.ts @@ -1,6 +1,11 @@ -import {ErrorRequestHandler, Request, RequestHandler, Response} from 'express' +import { + type ErrorRequestHandler, + type Request, + type RequestHandler, + type Response, +} from 'express' -import {AppContext} from '../context.js' +import {type AppContext} from '../context.js' import {httpLogger} from '../logger.js' export type Handler = (req: Request, res: Response) => Awaited @@ -34,3 +39,35 @@ export const errorHandler: ErrorRequestHandler = (err, req, res, next) => { } return res.status(500).end('server error') } + +export async function getImage(url: string) { + const response = await fetch(ensureJpeg(url)) + const arrayBuf = await response.arrayBuffer() + if (response.status !== 200) return null + return Buffer.from(arrayBuf) +} + +// CDN URLs end with @jpeg, @webp, or no extension (which may default to webp). +// We want to ensure the image URLs we use are for jpegs, required for compat with satori. +function ensureJpeg(url: string) { + return url.replace(/(@[a-z]{3,5})?$/, '@jpeg') +} + +export const hideAvatarLabels = new Set([ + '!hide', + '!warn', + 'porn', + 'sexual', + 'nudity', + 'sexual-figurative', + 'graphic-media', + 'gore', + 'self-harm', + 'sensitive', + 'security', + 'impersonation', + 'scam', + 'spam', + 'misleading', + 'inauthentic', +]) diff --git a/bskyogcard/src/types/bsky/index.ts b/bskyogcard/src/types/bsky/index.ts new file mode 100644 index 0000000000..2bcc6ef060 --- /dev/null +++ b/bskyogcard/src/types/bsky/index.ts @@ -0,0 +1,50 @@ +import {type ValidationResult} from '@atproto/lexicon' + +export * as profile from './profile.js' +export * as starterPack from './starterPack.js' + +/** + * Fast type checking without full schema validation, for use with data we + * trust, or for non-critical path use cases. Why? Our SDK's `is*` identity + * utils do not assert the type of the entire object, only the `$type` string. + * + * For full validation of the object schema, use the `validate` export from + * this file. + * + * Usage: + * ```ts + * import * as bsky from '#/types/bsky' + * + * if (bsky.dangerousIsType(item, AppBskyFeedPost.isRecord)) { + * // `item` has type `$Typed` here + * } + * ``` + */ +export function dangerousIsType( + record: unknown, + identity: (v: V) => v is V & {$type: NonNullable}, +): record is R { + return identity(record) +} + +/** + * Fully validates the object schema, which has a performance cost. + * + * For faster checks with data we trust, like that from our app view, use the + * `dangerousIsType` export from this same file. + * + * Usage: + * ```ts + * import * as bsky from '#/types/bsky' + * + * if (bsky.validate(item, AppBskyFeedPost.validateRecord)) { + * // `item` has type `$Typed` here + * } + * ``` + */ +export function validate( + record: unknown, + validator: (v: unknown) => ValidationResult, +): record is R { + return validator(record).success +} diff --git a/bskyogcard/src/types/bsky/profile.ts b/bskyogcard/src/types/bsky/profile.ts new file mode 100644 index 0000000000..12c8146ae1 --- /dev/null +++ b/bskyogcard/src/types/bsky/profile.ts @@ -0,0 +1,10 @@ +import {type AppBskyActorDefs, type ChatBskyActorDefs} from '@atproto/api' + +/** + * Matches any profile view exported by our SDK + */ +export type AnyProfileView = + | AppBskyActorDefs.ProfileViewBasic + | AppBskyActorDefs.ProfileView + | AppBskyActorDefs.ProfileViewDetailed + | ChatBskyActorDefs.ProfileViewBasic diff --git a/bskyogcard/src/types/bsky/starterPack.ts b/bskyogcard/src/types/bsky/starterPack.ts new file mode 100644 index 0000000000..0064e16bc6 --- /dev/null +++ b/bskyogcard/src/types/bsky/starterPack.ts @@ -0,0 +1,11 @@ +import {AppBskyGraphDefs} from '@atproto/api' + +export const isBasicView = AppBskyGraphDefs.isStarterPackViewBasic +export const isView = AppBskyGraphDefs.isStarterPackView + +/** + * Matches any starter pack view exported by our SDK + */ +export type AnyStarterPackView = + | AppBskyGraphDefs.StarterPackViewBasic + | AppBskyGraphDefs.StarterPackView diff --git a/bskyogcard/tsconfig.json b/bskyogcard/tsconfig.json index a5c3beecb1..77f3d5b4e7 100644 --- a/bskyogcard/tsconfig.json +++ b/bskyogcard/tsconfig.json @@ -3,9 +3,12 @@ "target": "ESNext", "module": "NodeNext", "esModuleInterop": true, + "erasableSyntaxOnly": true, "moduleResolution": "NodeNext", "jsx": "react-jsx", - "outDir": "dist" + "rootDir": "./src", + "outDir": "dist", + "skipLibCheck": true }, - "include": ["./src/index.ts", "./src/bin.ts"] + "include": ["./src/**/*"] } diff --git a/bskyogcard/yarn.lock b/bskyogcard/yarn.lock deleted file mode 100644 index c2fb0b6d1c..0000000000 --- a/bskyogcard/yarn.lock +++ /dev/null @@ -1,1638 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@atproto/api@0.12.19-next.0": - version "0.12.19-next.0" - resolved "https://registry.npmjs.org/@atproto/api/-/api-0.12.19-next.0.tgz" - integrity sha512-wyWr4uIabTgDTBY99y3QyrFxcIx1Mh4DkURgSv8sd/b+w0lfrZAJh0Gg9BXdg/iIjcf/M2lCTL04r0vASfkMVg== - dependencies: - "@atproto/common-web" "^0.3.0" - "@atproto/lexicon" "^0.4.0" - "@atproto/syntax" "^0.3.0" - "@atproto/xrpc" "^0.5.0" - multiformats "^9.9.0" - tlds "^1.234.0" - -"@atproto/common-web@^0.3.0": - version "0.3.0" - resolved "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.3.0.tgz" - integrity sha512-67VnV6JJyX+ZWyjV7xFQMypAgDmjVaR9ZCuU/QW+mqlqI7fex2uL4Fv+7/jHadgzhuJHVd6OHOvNn0wR5WZYtA== - dependencies: - graphemer "^1.4.0" - multiformats "^9.9.0" - uint8arrays "3.0.0" - zod "^3.21.4" - -"@atproto/common@^0.4.0": - version "0.4.0" - resolved "https://registry.npmjs.org/@atproto/common/-/common-0.4.0.tgz" - integrity sha512-yOXuPlCjT/OK9j+neIGYn9wkxx/AlxQSucysAF0xgwu0Ji8jAtKBf9Jv6R5ObYAjAD/kVUvEYumle+Yq/R9/7g== - dependencies: - "@atproto/common-web" "^0.3.0" - "@ipld/dag-cbor" "^7.0.3" - cbor-x "^1.5.1" - iso-datestring-validator "^2.2.2" - multiformats "^9.9.0" - pino "^8.15.0" - -"@atproto/lexicon@^0.4.0": - version "0.4.0" - resolved "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.4.0.tgz" - integrity sha512-RvCBKdSI4M8qWm5uTNz1z3R2yIvIhmOsMuleOj8YR6BwRD+QbtUBy3l+xQ7iXf4M5fdfJFxaUNa6Ty0iRwdKqQ== - dependencies: - "@atproto/common-web" "^0.3.0" - "@atproto/syntax" "^0.3.0" - iso-datestring-validator "^2.2.2" - multiformats "^9.9.0" - zod "^3.21.4" - -"@atproto/syntax@^0.3.0": - version "0.3.0" - resolved "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.3.0.tgz" - integrity sha512-Weq0ZBxffGHDXHl9U7BQc2BFJi/e23AL+k+i5+D9hUq/bzT4yjGsrCejkjq0xt82xXDjmhhvQSZ0LqxyZ5woxA== - -"@atproto/xrpc@^0.5.0": - version "0.5.0" - resolved "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.5.0.tgz" - integrity sha512-swu+wyOLvYW4l3n+VAuJbHcPcES+tin2Lsrp8Bw5aIXIICiuFn1YMFlwK9JwVUzTH21Py1s1nHEjr4CJeElJog== - dependencies: - "@atproto/lexicon" "^0.4.0" - zod "^3.21.4" - -"@cbor-extract/cbor-extract-darwin-arm64@2.2.0": - version "2.2.0" - resolved "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.2.0.tgz" - integrity sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w== - -"@cbor-extract/cbor-extract-darwin-x64@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-darwin-x64/-/cbor-extract-darwin-x64-2.2.0.tgz#9fbec199c888c5ec485a1839f4fad0485ab6c40a" - integrity sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w== - -"@cbor-extract/cbor-extract-linux-arm64@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-linux-arm64/-/cbor-extract-linux-arm64-2.2.0.tgz#bf77e0db4a1d2200a5aa072e02210d5043e953ae" - integrity sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ== - -"@cbor-extract/cbor-extract-linux-arm@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-linux-arm/-/cbor-extract-linux-arm-2.2.0.tgz#491335037eb8533ed8e21b139c59f6df04e39709" - integrity sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q== - -"@cbor-extract/cbor-extract-linux-x64@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-linux-x64/-/cbor-extract-linux-x64-2.2.0.tgz#672574485ccd24759bf8fb8eab9dbca517d35b97" - integrity sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw== - -"@cbor-extract/cbor-extract-win32-x64@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-win32-x64/-/cbor-extract-win32-x64-2.2.0.tgz#4b3f07af047f984c082de34b116e765cb9af975f" - integrity sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w== - -"@cspotcode/source-map-support@^0.8.0": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" - integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== - dependencies: - "@jridgewell/trace-mapping" "0.3.9" - -"@ipld/dag-cbor@^7.0.3": - version "7.0.3" - resolved "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-7.0.3.tgz" - integrity sha512-1VVh2huHsuohdXC1bGJNE8WR72slZ9XE2T3wbBBq31dm7ZBatmKLLxrB+XAqafxfRFjv08RZmj/W/ZqaM13AuA== - dependencies: - cborg "^1.6.0" - multiformats "^9.5.4" - -"@jridgewell/resolve-uri@^3.0.3": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== - -"@jridgewell/trace-mapping@0.3.9": - version "0.3.9" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" - integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@opentelemetry/api@^1.4.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" - integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== - -"@resvg/resvg-js-android-arm-eabi@2.6.2": - version "2.6.2" - resolved "https://registry.yarnpkg.com/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.6.2.tgz#e761e0b688127db64879f455178c92468a9aeabe" - integrity sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA== - -"@resvg/resvg-js-android-arm64@2.6.2": - version "2.6.2" - resolved "https://registry.yarnpkg.com/@resvg/resvg-js-android-arm64/-/resvg-js-android-arm64-2.6.2.tgz#b8cb564d7f6b3f37d9b43129f5dc5fe171e249e4" - integrity sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ== - -"@resvg/resvg-js-darwin-arm64@2.6.2": - version "2.6.2" - resolved "https://registry.npmjs.org/@resvg/resvg-js-darwin-arm64/-/resvg-js-darwin-arm64-2.6.2.tgz" - integrity sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A== - -"@resvg/resvg-js-darwin-x64@2.6.2": - version "2.6.2" - resolved "https://registry.yarnpkg.com/@resvg/resvg-js-darwin-x64/-/resvg-js-darwin-x64-2.6.2.tgz#e1344173aa27bfb4d880ab576d1acf1c1648faca" - integrity sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw== - -"@resvg/resvg-js-linux-arm-gnueabihf@2.6.2": - version "2.6.2" - resolved "https://registry.yarnpkg.com/@resvg/resvg-js-linux-arm-gnueabihf/-/resvg-js-linux-arm-gnueabihf-2.6.2.tgz#34c445eba45efd68f6130b2ab426d76a7424253d" - integrity sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw== - -"@resvg/resvg-js-linux-arm64-gnu@2.6.2": - version "2.6.2" - resolved "https://registry.yarnpkg.com/@resvg/resvg-js-linux-arm64-gnu/-/resvg-js-linux-arm64-gnu-2.6.2.tgz#30da47087dd8153182198b94fe9f8d994890dae5" - integrity sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg== - -"@resvg/resvg-js-linux-arm64-musl@2.6.2": - version "2.6.2" - resolved "https://registry.yarnpkg.com/@resvg/resvg-js-linux-arm64-musl/-/resvg-js-linux-arm64-musl-2.6.2.tgz#5d75b8ff5c83103729c1ca3779987302753c50d4" - integrity sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg== - -"@resvg/resvg-js-linux-x64-gnu@2.6.2": - version "2.6.2" - resolved "https://registry.yarnpkg.com/@resvg/resvg-js-linux-x64-gnu/-/resvg-js-linux-x64-gnu-2.6.2.tgz#411abedfaee5edc57cbb7701736cecba522e26f3" - integrity sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw== - -"@resvg/resvg-js-linux-x64-musl@2.6.2": - version "2.6.2" - resolved "https://registry.yarnpkg.com/@resvg/resvg-js-linux-x64-musl/-/resvg-js-linux-x64-musl-2.6.2.tgz#fe4984038f0372f279e3ff570b72934dd7eb2a5c" - integrity sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ== - -"@resvg/resvg-js-win32-arm64-msvc@2.6.2": - version "2.6.2" - resolved "https://registry.yarnpkg.com/@resvg/resvg-js-win32-arm64-msvc/-/resvg-js-win32-arm64-msvc-2.6.2.tgz#d3a053cf7ff687087a2106330c0fdaae706254d1" - integrity sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ== - -"@resvg/resvg-js-win32-ia32-msvc@2.6.2": - version "2.6.2" - resolved "https://registry.yarnpkg.com/@resvg/resvg-js-win32-ia32-msvc/-/resvg-js-win32-ia32-msvc-2.6.2.tgz#7cdda1ce29ef7209e28191d917fa5bef0624a4ad" - integrity sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w== - -"@resvg/resvg-js-win32-x64-msvc@2.6.2": - version "2.6.2" - resolved "https://registry.yarnpkg.com/@resvg/resvg-js-win32-x64-msvc/-/resvg-js-win32-x64-msvc-2.6.2.tgz#cb0ad04525d65f3def4c8d346157a57976d5b388" - integrity sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ== - -"@resvg/resvg-js@^2.6.2": - version "2.6.2" - resolved "https://registry.npmjs.org/@resvg/resvg-js/-/resvg-js-2.6.2.tgz" - integrity sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q== - optionalDependencies: - "@resvg/resvg-js-android-arm-eabi" "2.6.2" - "@resvg/resvg-js-android-arm64" "2.6.2" - "@resvg/resvg-js-darwin-arm64" "2.6.2" - "@resvg/resvg-js-darwin-x64" "2.6.2" - "@resvg/resvg-js-linux-arm-gnueabihf" "2.6.2" - "@resvg/resvg-js-linux-arm64-gnu" "2.6.2" - "@resvg/resvg-js-linux-arm64-musl" "2.6.2" - "@resvg/resvg-js-linux-x64-gnu" "2.6.2" - "@resvg/resvg-js-linux-x64-musl" "2.6.2" - "@resvg/resvg-js-win32-arm64-msvc" "2.6.2" - "@resvg/resvg-js-win32-ia32-msvc" "2.6.2" - "@resvg/resvg-js-win32-x64-msvc" "2.6.2" - -"@shuding/opentype.js@1.4.0-beta.0": - version "1.4.0-beta.0" - resolved "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz" - integrity sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA== - dependencies: - fflate "^0.7.3" - string.prototype.codepointat "^0.2.1" - -"@tsconfig/node10@^1.0.7": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" - integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== - -"@tsconfig/node12@^1.0.7": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" - integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== - -"@tsconfig/node14@^1.0.0": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" - integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== - -"@tsconfig/node16@^1.0.2": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" - integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== - -"@types/body-parser@*": - version "1.19.6" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.6.tgz#1859bebb8fd7dac9918a45d54c1971ab8b5af474" - integrity sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.38" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" - integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== - dependencies: - "@types/node" "*" - -"@types/express-serve-static-core@^4.17.33": - version "4.19.6" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz#e01324c2a024ff367d92c66f48553ced0ab50267" - integrity sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - "@types/send" "*" - -"@types/express@^4.17.21": - version "4.17.23" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.23.tgz#35af3193c640bfd4d7fe77191cd0ed411a433bef" - integrity sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.33" - "@types/qs" "*" - "@types/serve-static" "*" - -"@types/http-errors@*": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" - integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== - -"@types/mime@^1": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" - integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== - -"@types/node@*": - version "24.5.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-24.5.2.tgz#52ceb83f50fe0fcfdfbd2a9fab6db2e9e7ef6446" - integrity sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ== - dependencies: - undici-types "~7.12.0" - -"@types/node@^20.14.3": - version "20.14.3" - resolved "https://registry.npmjs.org/@types/node/-/node-20.14.3.tgz" - integrity sha512-Nuzqa6WAxeGnve6SXqiPAM9rA++VQs+iLZ1DDd56y0gdvygSZlQvZuvdFPR3yLqkVxPu4WrO02iDEyH1g+wazw== - dependencies: - undici-types "~5.26.4" - -"@types/qs@*": - version "6.14.0" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5" - integrity sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ== - -"@types/range-parser@*": - version "1.2.7" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" - integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== - -"@types/send@*": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.5.tgz#d991d4f2b16f2b1ef497131f00a9114290791e74" - integrity sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w== - dependencies: - "@types/mime" "^1" - "@types/node" "*" - -"@types/serve-static@*": - version "1.15.8" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.8.tgz#8180c3fbe4a70e8f00b9f70b9ba7f08f35987877" - integrity sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg== - dependencies: - "@types/http-errors" "*" - "@types/node" "*" - "@types/send" "*" - -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - -accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - -acorn-walk@^8.1.1: - version "8.3.3" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.3.tgz#9caeac29eefaa0c41e3d4c65137de4d6f34df43e" - integrity sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw== - dependencies: - acorn "^8.11.0" - -acorn@^8.11.0, acorn@^8.4.1: - version "8.12.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.0.tgz#1627bfa2e058148036133b8d9b51a700663c294c" - integrity sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw== - -arg@^4.1.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" - integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz" - integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== - -atomic-sleep@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz" - integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== - -base64-js@0.0.8: - version "0.0.8" - resolved "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz" - integrity sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw== - -base64-js@^1.3.1: - version "1.5.1" - resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -bintrees@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.2.tgz#49f896d6e858a4a499df85c38fb399b9aff840f8" - integrity sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw== - -body-parser@1.20.2: - version "1.20.2" - resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz" - integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== - dependencies: - bytes "3.1.2" - content-type "~1.0.5" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.11.0" - raw-body "2.5.2" - type-is "~1.6.18" - unpipe "1.0.0" - -body-parser@1.20.3: - version "1.20.3" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" - integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== - dependencies: - bytes "3.1.2" - content-type "~1.0.5" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.13.0" - raw-body "2.5.2" - type-is "~1.6.18" - unpipe "1.0.0" - -boolean@^3.1.4: - version "3.2.0" - resolved "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz" - integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw== - -buffer@^6.0.3: - version "6.0.3" - resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz" - integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.2.1" - -bytes@3.1.2: - version "3.1.2" - resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - -call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" - integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - -call-bind@^1.0.7: - version "1.0.7" - resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz" - integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - set-function-length "^1.2.1" - -call-bound@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" - integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== - dependencies: - call-bind-apply-helpers "^1.0.2" - get-intrinsic "^1.3.0" - -camelize@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz" - integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ== - -cbor-extract@^2.2.0: - version "2.2.0" - resolved "https://registry.npmjs.org/cbor-extract/-/cbor-extract-2.2.0.tgz" - integrity sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA== - dependencies: - node-gyp-build-optional-packages "5.1.1" - optionalDependencies: - "@cbor-extract/cbor-extract-darwin-arm64" "2.2.0" - "@cbor-extract/cbor-extract-darwin-x64" "2.2.0" - "@cbor-extract/cbor-extract-linux-arm" "2.2.0" - "@cbor-extract/cbor-extract-linux-arm64" "2.2.0" - "@cbor-extract/cbor-extract-linux-x64" "2.2.0" - "@cbor-extract/cbor-extract-win32-x64" "2.2.0" - -cbor-x@^1.5.1: - version "1.5.9" - resolved "https://registry.npmjs.org/cbor-x/-/cbor-x-1.5.9.tgz" - integrity sha512-OEI5rEu3MeR0WWNUXuIGkxmbXVhABP+VtgAXzm48c9ulkrsvxshjjk94XSOGphyAKeNGLPfAxxzEtgQ6rEVpYQ== - optionalDependencies: - cbor-extract "^2.2.0" - -cborg@^1.6.0: - version "1.10.2" - resolved "https://registry.npmjs.org/cborg/-/cborg-1.10.2.tgz" - integrity sha512-b3tFPA9pUr2zCUiCfRd2+wok2/LBSNUMKOuRRok+WlvvAgEt/PlbgPTsZUcwCOs53IJvLgTp0eotwtosE6njug== - -color-name@^1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -content-disposition@0.5.4: - version "0.5.4" - resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -content-type@~1.0.4, content-type@~1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz" - integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz" - integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== - -cookie@0.6.0: - version "0.6.0" - resolved "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz" - integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== - -cookie@0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9" - integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== - -create-require@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" - integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== - -css-background-parser@^0.1.0: - version "0.1.0" - resolved "https://registry.npmjs.org/css-background-parser/-/css-background-parser-0.1.0.tgz" - integrity sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA== - -css-box-shadow@1.0.0-3: - version "1.0.0-3" - resolved "https://registry.npmjs.org/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz" - integrity sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg== - -css-color-keywords@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz" - integrity sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg== - -css-to-react-native@^3.0.0: - version "3.2.0" - resolved "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz" - integrity sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ== - dependencies: - camelize "^1.0.0" - css-color-keywords "^1.0.0" - postcss-value-parser "^4.0.2" - -debug@2.6.9: - version "2.6.9" - resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -define-data-property@^1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz" - integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - gopd "^1.0.1" - -delay@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz" - integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== - -depd@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -destroy@1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - -detect-libc@^2.0.1: - version "2.0.3" - resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz" - integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== - -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - -dunder-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" - integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== - dependencies: - call-bind-apply-helpers "^1.0.1" - es-errors "^1.3.0" - gopd "^1.2.0" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - -emoji-regex@^10.2.1: - version "10.3.0" - resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz" - integrity sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - -encodeurl@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" - integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== - -es-define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz" - integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== - dependencies: - get-intrinsic "^1.2.4" - -es-define-property@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" - integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== - -es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" - integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== - dependencies: - es-errors "^1.3.0" - -escape-html@^1.0.3, escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" - integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - -events@^3.3.0: - version "3.3.0" - resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -express-prom-bundle@^7.0.0: - version "7.0.2" - resolved "https://registry.yarnpkg.com/express-prom-bundle/-/express-prom-bundle-7.0.2.tgz#73a83b9033639dae81c78906a9df78844f3c93fa" - integrity sha512-ffFV4HGHvCKnkNJFqm42sYztRJE5mLgOj8MpGey1HOatuFhtcwXoBD2m5gca7ZbcyjkIf7lOH5ZdrhlrBf0sGw== - dependencies: - "@types/express" "^4.17.21" - express "^4.18.2" - on-finished "^2.3.0" - url-value-parser "^2.0.0" - -express@^4.18.2: - version "4.21.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.21.2.tgz#cf250e48362174ead6cea4a566abef0162c1ec32" - integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.20.3" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.7.1" - cookie-signature "1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~2.0.0" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.3.1" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.3" - methods "~1.1.2" - on-finished "2.4.1" - parseurl "~1.3.3" - path-to-regexp "0.1.12" - proxy-addr "~2.0.7" - qs "6.13.0" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.19.0" - serve-static "1.16.2" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -express@^4.19.2: - version "4.19.2" - resolved "https://registry.npmjs.org/express/-/express-4.19.2.tgz" - integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.20.2" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.6.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.2.0" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "2.4.1" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.7" - qs "6.11.0" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.18.0" - serve-static "1.15.0" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -fast-printf@^1.6.9: - version "1.6.9" - resolved "https://registry.npmjs.org/fast-printf/-/fast-printf-1.6.9.tgz" - integrity sha512-FChq8hbz65WMj4rstcQsFB0O7Cy++nmbNfLYnD9cYv2cRn8EG6k/MGn9kO/tjO66t09DLDugj3yL+V2o6Qftrg== - dependencies: - boolean "^3.1.4" - -fast-redact@^3.1.1: - version "3.5.0" - resolved "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz" - integrity sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A== - -fflate@^0.7.3: - version "0.7.4" - resolved "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz" - integrity sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw== - -finalhandler@1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz" - integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "2.4.1" - parseurl "~1.3.3" - statuses "2.0.1" - unpipe "~1.0.0" - -finalhandler@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019" - integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== - dependencies: - debug "2.6.9" - encodeurl "~2.0.0" - escape-html "~1.0.3" - on-finished "2.4.1" - parseurl "~1.3.3" - statuses "2.0.1" - unpipe "~1.0.0" - -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz" - integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== - -fs-extra@^8.0.1: - version "8.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" - integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^4.0.0" - universalify "^0.1.0" - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -get-intrinsic@^1.1.3, get-intrinsic@^1.2.4: - version "1.2.4" - resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz" - integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - has-proto "^1.0.1" - has-symbols "^1.0.3" - hasown "^2.0.0" - -get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" - integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== - dependencies: - call-bind-apply-helpers "^1.0.2" - es-define-property "^1.0.1" - es-errors "^1.3.0" - es-object-atoms "^1.1.1" - function-bind "^1.1.2" - get-proto "^1.0.1" - gopd "^1.2.0" - has-symbols "^1.1.0" - hasown "^2.0.2" - math-intrinsics "^1.1.0" - -get-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" - integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== - dependencies: - dunder-proto "^1.0.1" - es-object-atoms "^1.0.0" - -gopd@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz" - integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== - dependencies: - get-intrinsic "^1.1.3" - -gopd@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" - integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== - -graceful-fs@^4.1.6, graceful-fs@^4.2.0: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -graphemer@^1.4.0: - version "1.4.0" - resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz" - integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== - -has-property-descriptors@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz" - integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== - dependencies: - es-define-property "^1.0.0" - -has-proto@^1.0.1: - version "1.0.3" - resolved "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz" - integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== - -has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -has-symbols@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" - integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== - -hasown@^2.0.0, hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - -hex-rgb@^4.1.0: - version "4.3.0" - resolved "https://registry.npmjs.org/hex-rgb/-/hex-rgb-4.3.0.tgz" - integrity sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw== - -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - -http-terminator@^3.2.0: - version "3.2.0" - resolved "https://registry.npmjs.org/http-terminator/-/http-terminator-3.2.0.tgz" - integrity sha512-JLjck1EzPaWjsmIf8bziM3p9fgR1Y3JoUKAkyYEbZmFrIvJM6I8vVJfBGWlEtV9IWOvzNnaTtjuwZeBY2kwB4g== - dependencies: - delay "^5.0.0" - p-wait-for "^3.2.0" - roarr "^7.0.4" - type-fest "^2.3.3" - -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -ieee754@^1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - -inherits@2.0.4: - version "2.0.4" - resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -iso-datestring-validator@^2.2.2: - version "2.2.2" - resolved "https://registry.npmjs.org/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz" - integrity sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA== - -"js-tokens@^3.0.0 || ^4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -jsonfile@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== - optionalDependencies: - graceful-fs "^4.1.6" - -jsonfile@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-5.0.0.tgz#e6b718f73da420d612823996fdf14a03f6ff6922" - integrity sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w== - dependencies: - universalify "^0.1.2" - optionalDependencies: - graceful-fs "^4.1.6" - -linebreak@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz" - integrity sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ== - dependencies: - base64-js "0.0.8" - unicode-trie "^2.0.0" - -loose-envify@^1.1.0: - version "1.4.0" - resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" - integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== - dependencies: - js-tokens "^3.0.0 || ^4.0.0" - -make-error@^1.1.1: - version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - -math-intrinsics@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" - integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz" - integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== - -merge-descriptors@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" - integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@~2.1.24, mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - -ms@2.1.3: - version "2.1.3" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -multiformats@^9.4.2, multiformats@^9.5.4, multiformats@^9.9.0: - version "9.9.0" - resolved "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz" - integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg== - -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - -node-gyp-build-optional-packages@5.1.1: - version "5.1.1" - resolved "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz" - integrity sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw== - dependencies: - detect-libc "^2.0.1" - -object-inspect@^1.13.1: - version "1.13.1" - resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz" - integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== - -object-inspect@^1.13.3: - version "1.13.4" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" - integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== - -on-exit-leak-free@^2.1.0: - version "2.1.2" - resolved "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz" - integrity sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA== - -on-finished@2.4.1, on-finished@^2.3.0: - version "2.4.1" - resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz" - integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== - -p-timeout@^3.0.0: - version "3.2.0" - resolved "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz" - integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== - dependencies: - p-finally "^1.0.0" - -p-wait-for@^3.2.0: - version "3.2.0" - resolved "https://registry.npmjs.org/p-wait-for/-/p-wait-for-3.2.0.tgz" - integrity sha512-wpgERjNkLrBiFmkMEjuZJEWKKDrNfHCKA1OhyN1wg1FrLkULbviEy6py1AyJUgZ72YWFbZ38FIpnqvVqAlDUwA== - dependencies: - p-timeout "^3.0.0" - -pako@^0.2.5: - version "0.2.9" - resolved "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz" - integrity sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA== - -parse-css-color@^0.2.1: - version "0.2.1" - resolved "https://registry.npmjs.org/parse-css-color/-/parse-css-color-0.2.1.tgz" - integrity sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg== - dependencies: - color-name "^1.1.4" - hex-rgb "^4.1.0" - -parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -path-to-regexp@0.1.12: - version "0.1.12" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" - integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== - -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" - integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== - -pino-abstract-transport@^1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz" - integrity sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q== - dependencies: - readable-stream "^4.0.0" - split2 "^4.0.0" - -pino-std-serializers@^6.0.0: - version "6.2.2" - resolved "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz" - integrity sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA== - -pino-std-serializers@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz" - integrity sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA== - -pino@^8.15.0: - version "8.21.0" - resolved "https://registry.npmjs.org/pino/-/pino-8.21.0.tgz" - integrity sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q== - dependencies: - atomic-sleep "^1.0.0" - fast-redact "^3.1.1" - on-exit-leak-free "^2.1.0" - pino-abstract-transport "^1.2.0" - pino-std-serializers "^6.0.0" - process-warning "^3.0.0" - quick-format-unescaped "^4.0.3" - real-require "^0.2.0" - safe-stable-stringify "^2.3.1" - sonic-boom "^3.7.0" - thread-stream "^2.6.0" - -pino@^9.2.0: - version "9.2.0" - resolved "https://registry.npmjs.org/pino/-/pino-9.2.0.tgz" - integrity sha512-g3/hpwfujK5a4oVbaefoJxezLzsDgLcNJeITvC6yrfwYeT9la+edCK42j5QpEQSQCZgTKapXvnQIdgZwvRaZug== - dependencies: - atomic-sleep "^1.0.0" - fast-redact "^3.1.1" - on-exit-leak-free "^2.1.0" - pino-abstract-transport "^1.2.0" - pino-std-serializers "^7.0.0" - process-warning "^3.0.0" - quick-format-unescaped "^4.0.3" - real-require "^0.2.0" - safe-stable-stringify "^2.3.1" - sonic-boom "^4.0.1" - thread-stream "^3.0.0" - -postcss-value-parser@^4.0.2, postcss-value-parser@^4.2.0: - version "4.2.0" - resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" - integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== - -process-warning@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz" - integrity sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ== - -process@^0.11.10: - version "0.11.10" - resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz" - integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== - -prom-client@^15.1.3: - version "15.1.3" - resolved "https://registry.yarnpkg.com/prom-client/-/prom-client-15.1.3.tgz#69fa8de93a88bc9783173db5f758dc1c69fa8fc2" - integrity sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g== - dependencies: - "@opentelemetry/api" "^1.4.0" - tdigest "^0.1.1" - -proxy-addr@~2.0.7: - version "2.0.7" - resolved "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - -qs@6.11.0: - version "6.11.0" - resolved "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz" - integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== - dependencies: - side-channel "^1.0.4" - -qs@6.13.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" - integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== - dependencies: - side-channel "^1.0.6" - -quick-format-unescaped@^4.0.3: - version "4.0.4" - resolved "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz" - integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== - -range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.5.2: - version "2.5.2" - resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz" - integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - -react@^18.3.1: - version "18.3.1" - resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz" - integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== - dependencies: - loose-envify "^1.1.0" - -readable-stream@^4.0.0: - version "4.5.2" - resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz" - integrity sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g== - dependencies: - abort-controller "^3.0.0" - buffer "^6.0.3" - events "^3.3.0" - process "^0.11.10" - string_decoder "^1.3.0" - -real-require@^0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz" - integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg== - -roarr@^7.0.4: - version "7.21.1" - resolved "https://registry.npmjs.org/roarr/-/roarr-7.21.1.tgz" - integrity sha512-3niqt5bXFY1InKU8HKWqqYTYjtrBaxBMnXELXCXUYgtNYGUtZM5rB46HIC430AyacL95iEniGf7RgqsesykLmQ== - dependencies: - fast-printf "^1.6.9" - safe-stable-stringify "^2.4.3" - semver-compare "^1.0.0" - -safe-buffer@5.2.1, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-stable-stringify@^2.3.1, safe-stable-stringify@^2.4.3: - version "2.4.3" - resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz" - integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== - -"safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -satori@^0.10.13: - version "0.10.13" - resolved "https://registry.npmjs.org/satori/-/satori-0.10.13.tgz" - integrity sha512-klCwkVYMQ/ZN5inJLHzrUmGwoRfsdP7idB5hfpJ1jfiJk1ErDitK8Hkc6Kll1+Ox2WtqEuGecSZLnmup3CGzvQ== - dependencies: - "@shuding/opentype.js" "1.4.0-beta.0" - css-background-parser "^0.1.0" - css-box-shadow "1.0.0-3" - css-to-react-native "^3.0.0" - emoji-regex "^10.2.1" - escape-html "^1.0.3" - linebreak "^1.1.0" - parse-css-color "^0.2.1" - postcss-value-parser "^4.2.0" - yoga-wasm-web "^0.3.3" - -semver-compare@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz" - integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow== - -send@0.18.0: - version "0.18.0" - resolved "https://registry.npmjs.org/send/-/send-0.18.0.tgz" - integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - -send@0.19.0: - version "0.19.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" - integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - -serve-static@1.15.0: - version "1.15.0" - resolved "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz" - integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.18.0" - -serve-static@1.16.2: - version "1.16.2" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" - integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== - dependencies: - encodeurl "~2.0.0" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.19.0" - -set-function-length@^1.2.1: - version "1.2.2" - resolved "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz" - integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -side-channel-list@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" - integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== - dependencies: - es-errors "^1.3.0" - object-inspect "^1.13.3" - -side-channel-map@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" - integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - get-intrinsic "^1.2.5" - object-inspect "^1.13.3" - -side-channel-weakmap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" - integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - get-intrinsic "^1.2.5" - object-inspect "^1.13.3" - side-channel-map "^1.0.1" - -side-channel@^1.0.4: - version "1.0.6" - resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz" - integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - get-intrinsic "^1.2.4" - object-inspect "^1.13.1" - -side-channel@^1.0.6: - version "1.1.0" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" - integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== - dependencies: - es-errors "^1.3.0" - object-inspect "^1.13.3" - side-channel-list "^1.0.0" - side-channel-map "^1.0.1" - side-channel-weakmap "^1.0.2" - -sonic-boom@^3.7.0: - version "3.8.1" - resolved "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz" - integrity sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg== - dependencies: - atomic-sleep "^1.0.0" - -sonic-boom@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.0.1.tgz" - integrity sha512-hTSD/6JMLyT4r9zeof6UtuBDpjJ9sO08/nmS5djaA9eozT9oOlNdpXSnzcgj4FTqpk3nkLrs61l4gip9r1HCrQ== - dependencies: - atomic-sleep "^1.0.0" - -split2@^4.0.0: - version "4.2.0" - resolved "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz" - integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== - -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -string.prototype.codepointat@^0.2.1: - version "0.2.1" - resolved "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz" - integrity sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg== - -string_decoder@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -tdigest@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/tdigest/-/tdigest-0.1.2.tgz#96c64bac4ff10746b910b0e23b515794e12faced" - integrity sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA== - dependencies: - bintrees "1.0.2" - -thread-stream@^2.6.0: - version "2.7.0" - resolved "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz" - integrity sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw== - dependencies: - real-require "^0.2.0" - -thread-stream@^3.0.0: - version "3.1.0" - resolved "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz" - integrity sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A== - dependencies: - real-require "^0.2.0" - -tiny-inflate@^1.0.0: - version "1.0.3" - resolved "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz" - integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw== - -tlds@^1.234.0: - version "1.252.0" - resolved "https://registry.npmjs.org/tlds/-/tlds-1.252.0.tgz" - integrity sha512-GA16+8HXvqtfEnw/DTcwB0UU354QE1n3+wh08oFjr6Znl7ZLAeUgYzCcK+/CCrOyE0vnHR8/pu3XXG3vDijXpQ== - -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -ts-node@^10.9.2: - version "10.9.2" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" - integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== - dependencies: - "@cspotcode/source-map-support" "^0.8.0" - "@tsconfig/node10" "^1.0.7" - "@tsconfig/node12" "^1.0.7" - "@tsconfig/node14" "^1.0.0" - "@tsconfig/node16" "^1.0.2" - acorn "^8.4.1" - acorn-walk "^8.1.1" - arg "^4.1.0" - create-require "^1.1.0" - diff "^4.0.1" - make-error "^1.1.1" - v8-compile-cache-lib "^3.0.1" - yn "3.1.1" - -twemoji-parser@14.0.0: - version "14.0.0" - resolved "https://registry.yarnpkg.com/twemoji-parser/-/twemoji-parser-14.0.0.tgz#13dabcb6d3a261d9efbf58a1666b182033bf2b62" - integrity sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA== - -twemoji@^14.0.2: - version "14.0.2" - resolved "https://registry.yarnpkg.com/twemoji/-/twemoji-14.0.2.tgz#c53adb01dab22bf4870f648ca8cc347ce99ee37e" - integrity sha512-BzOoXIe1QVdmsUmZ54xbEH+8AgtOKUiG53zO5vVP2iUu6h5u9lN15NcuS6te4OY96qx0H7JK9vjjl9WQbkTRuA== - dependencies: - fs-extra "^8.0.1" - jsonfile "^5.0.0" - twemoji-parser "14.0.0" - universalify "^0.1.2" - -type-fest@^2.3.3: - version "2.19.0" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz" - integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== - -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -typescript@^5.4.5: - version "5.4.5" - resolved "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz" - integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== - -uint8arrays@3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.0.0.tgz" - integrity sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA== - dependencies: - multiformats "^9.4.2" - -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== - -undici-types@~7.12.0: - version "7.12.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.12.0.tgz#15c5c7475c2a3ba30659529f5cdb4674b622fafb" - integrity sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ== - -unicode-trie@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz" - integrity sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ== - dependencies: - pako "^0.2.5" - tiny-inflate "^1.0.0" - -universalify@^0.1.0, universalify@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" - integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== - -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - -url-value-parser@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/url-value-parser/-/url-value-parser-2.2.0.tgz#f38ae8cd24604ec69bc219d66929ddbbd93a2b32" - integrity sha512-yIQdxJpgkPamPPAPuGdS7Q548rLhny42tg8d4vyTNzFqvOnwqrgHXvgehT09U7fwrzxi3RxCiXjoNUNnNOlQ8A== - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" - integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - -v8-compile-cache-lib@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" - integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== - -vary@~1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - -yn@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" - integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== - -yoga-wasm-web@^0.3.3: - version "0.3.3" - resolved "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz" - integrity sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA== - -zod@^3.21.4: - version "3.23.8" - resolved "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz" - integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== diff --git a/bskyweb/README.md b/bskyweb/README.md index b682929975..f768b70609 100644 --- a/bskyweb/README.md +++ b/bskyweb/README.md @@ -10,13 +10,13 @@ like: # install nodejs nvm install nvm use -npm install --global yarn +npm install --global pnpm # setup tools and deps (in top level of this repo) -yarn install --frozen-lockfile +pnpm install --frozen-lockfile -# run yarn web dev server, if you wanted -yarn web +# run pnpm web dev server, if you wanted +pnpm web ``` Then build and copy over the big 'ol `bundle.web.js` file: @@ -24,7 +24,7 @@ Then build and copy over the big 'ol `bundle.web.js` file: ```bash # in the top level of this repo -yarn build-web +pnpm build-web ``` ### Golang Daemon diff --git a/bskyweb/cmd/bskyweb/embedmeta.go b/bskyweb/cmd/bskyweb/embedmeta.go new file mode 100644 index 0000000000..03da46273e --- /dev/null +++ b/bskyweb/cmd/bskyweb/embedmeta.go @@ -0,0 +1,37 @@ +package main + +import ( + appbsky "github.com/bluesky-social/indigo/api/bsky" +) + +// Helpers for extracting Open Graph metadata from post embeds. og:video +// shares findVideoEmbed with the JSON-LD VideoObject path in jsonld.go so +// the two outputs cannot drift. + +// videoMeta holds og:video meta tag data. +type videoMeta struct { + URL string + Type string + Width int64 + Height int64 + HasSize bool +} + +// extractVideoMeta returns og:video metadata, or the zero value if there's +// no video embed. Respects embedHidden so og:video and og:image stay in sync. +func extractVideoMeta(pv *appbsky.FeedDefs_PostView, embedHidden bool) videoMeta { + v := findVideoEmbed(pv, embedHidden) + if v == nil || v.Playlist == "" { + return videoMeta{} + } + out := videoMeta{ + URL: v.Playlist, + Type: "application/vnd.apple.mpegurl", + } + if v.AspectRatio != nil { + out.Width = v.AspectRatio.Width + out.Height = v.AspectRatio.Height + out.HasSize = true + } + return out +} diff --git a/bskyweb/cmd/bskyweb/embedmeta_test.go b/bskyweb/cmd/bskyweb/embedmeta_test.go new file mode 100644 index 0000000000..8270520633 --- /dev/null +++ b/bskyweb/cmd/bskyweb/embedmeta_test.go @@ -0,0 +1,69 @@ +package main + +import ( + "testing" + + appbsky "github.com/bluesky-social/indigo/api/bsky" +) + +func TestExtractVideoMeta(t *testing.T) { + // Plain post — no video. + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc", "hi") + if vm := extractVideoMeta(pv, false); vm.URL != "" { + t.Errorf("non-video post produced video meta: %+v", vm) + } + + // Video embed with playlist + aspect ratio. + playlist := "https://video.bsky.app/playlist.m3u8" + pv = makePostView("alice.bsky.social", "did:plc:alice", "abc", "watch") + pv.Embed = &appbsky.FeedDefs_PostView_Embed{ + EmbedVideo_View: &appbsky.EmbedVideo_View{ + Thumbnail: strPtr("https://cdn.bsky.app/thumb@jpg"), + Playlist: playlist, + AspectRatio: &appbsky.EmbedDefs_AspectRatio{Width: 16, Height: 9}, + }, + } + vm := extractVideoMeta(pv, false) + if vm.URL != playlist { + t.Errorf("URL wrong: %v", vm.URL) + } + if vm.Type != "application/vnd.apple.mpegurl" { + t.Errorf("Type wrong: %v", vm.Type) + } + if !vm.HasSize || vm.Width != 16 || vm.Height != 9 { + t.Errorf("aspect ratio not propagated: %+v", vm) + } + + // Hidden embed gate suppresses video too. + if vm := extractVideoMeta(pv, true); vm.URL != "" { + t.Errorf("hidden embed should suppress video meta") + } + + // Video inside record-with-media. + pv2 := makePostView("alice.bsky.social", "did:plc:alice", "abc", "quote w/ video") + pv2.Embed = &appbsky.FeedDefs_PostView_Embed{ + EmbedRecordWithMedia_View: &appbsky.EmbedRecordWithMedia_View{ + Media: &appbsky.EmbedRecordWithMedia_View_Media{ + EmbedVideo_View: &appbsky.EmbedVideo_View{Playlist: playlist}, + }, + }, + } + vm2 := extractVideoMeta(pv2, false) + if vm2.URL != playlist { + t.Errorf("record-with-media video URL not extracted: %+v", vm2) + } + if vm2.HasSize { + t.Errorf("expected HasSize=false when no aspect ratio") + } + + // Video without playlist — skip entirely. + pv3 := makePostView("alice.bsky.social", "did:plc:alice", "abc", "no playlist") + pv3.Embed = &appbsky.FeedDefs_PostView_Embed{ + EmbedVideo_View: &appbsky.EmbedVideo_View{ + Thumbnail: strPtr("https://cdn.bsky.app/thumb@jpg"), + }, + } + if vm := extractVideoMeta(pv3, false); vm.URL != "" { + t.Errorf("video without playlist should produce empty meta, got %+v", vm) + } +} diff --git a/bskyweb/cmd/bskyweb/jsonld.go b/bskyweb/cmd/bskyweb/jsonld.go new file mode 100644 index 0000000000..4178250493 --- /dev/null +++ b/bskyweb/cmd/bskyweb/jsonld.go @@ -0,0 +1,690 @@ +package main + +import ( + "encoding/json" + "fmt" + + appbsky "github.com/bluesky-social/indigo/api/bsky" + "github.com/bluesky-social/indigo/atproto/syntax" +) + +// schema.org structured-data types emitted on post and profile pages. +// Building these as Go structs (rather than inline JSON in templates) gives +// us correct JSON escaping via encoding/json and a single place to test the +// schema. Optional fields use omitempty so empty values don't reach Google's +// validator as null/empty strings. + +const schemaOrgContext = "https://schema.org" + +type interactionStat struct { + Type string `json:"@type"` + InteractionType string `json:"interactionType"` + UserInteractionCount int64 `json:"userInteractionCount"` +} + +type personOrOrg struct { + Type string `json:"@type"` + Name string `json:"name,omitempty"` + AlternateName string `json:"alternateName,omitempty"` + Identifier string `json:"identifier,omitempty"` + URL string `json:"url,omitempty"` + Description string `json:"description,omitempty"` + Image string `json:"image,omitempty"` + InteractionStat []interactionStat `json:"interactionStatistic,omitempty"` + AgentInteractionStat []interactionStat `json:"agentInteractionStatistic,omitempty"` + ReviewedBy []*verifier `json:"reviewedBy,omitempty"` +} + +// verifier is the schema.org Person shape emitted under Person.reviewedBy. +// Narrower than personOrOrg: just enough to identify the verifying entity. +type verifier struct { + Type string `json:"@type"` + Name string `json:"name,omitempty"` + AlternateName string `json:"alternateName,omitempty"` + Identifier string `json:"identifier,omitempty"` + URL string `json:"url,omitempty"` +} + +type sharedContent struct { + Type string `json:"@type"` + URL string `json:"url"` +} + +type discussionForumPosting struct { + Context string `json:"@context,omitempty"` + Type string `json:"@type"` + URL string `json:"url,omitempty"` + Identifier string `json:"identifier,omitempty"` + Author *personOrOrg `json:"author,omitempty"` + Text string `json:"text,omitempty"` + Image []string `json:"image,omitempty"` + ThumbnailURL string `json:"thumbnailUrl,omitempty"` + Video *videoObject `json:"video,omitempty"` + DatePublished string `json:"datePublished,omitempty"` + InteractionStat []interactionStat `json:"interactionStatistic,omitempty"` + CommentCount *int64 `json:"commentCount,omitempty"` + Comment []comment `json:"comment,omitempty"` + IsBasedOn string `json:"isBasedOn,omitempty"` + SharedContent *sharedContent `json:"sharedContent,omitempty"` +} + +// videoObject is the schema.org VideoObject shape for video embeds. +// duration is omitted because the appview does not expose it. +type videoObject struct { + Type string `json:"@type"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + ThumbnailURL string `json:"thumbnailUrl,omitempty"` + UploadDate string `json:"uploadDate,omitempty"` + ContentURL string `json:"contentUrl,omitempty"` + EmbedURL string `json:"embedUrl,omitempty"` + Width int64 `json:"width,omitempty"` + Height int64 `json:"height,omitempty"` +} + +// comment is the schema.org Comment shape used in +// DiscussionForumPosting.comment[]. The comment property does not accept +// DiscussionForumPosting, so replies map to Comment. +type comment struct { + Type string `json:"@type"` + URL string `json:"url,omitempty"` + Identifier string `json:"identifier,omitempty"` + Author *personOrOrg `json:"author,omitempty"` + Text string `json:"text,omitempty"` + Image []string `json:"image,omitempty"` + ThumbnailURL string `json:"thumbnailUrl,omitempty"` + Video *videoObject `json:"video,omitempty"` + DatePublished string `json:"datePublished,omitempty"` +} + +type webPage struct { + Context string `json:"@context"` + Type string `json:"@type"` + URL string `json:"url,omitempty"` + MainEntity discussionForumPosting `json:"mainEntity"` +} + +type profilePage struct { + Context string `json:"@context"` + Type string `json:"@type"` + DateCreated string `json:"dateCreated,omitempty"` + MainEntity *personOrOrg `json:"mainEntity"` + HasPart []discussionForumPosting `json:"hasPart,omitempty"` +} + +// maxComments caps DiscussionForumPosting.comment[] to keep SSR HTML small. +const maxComments = 10 + +// maxRecentPosts caps ProfilePage.hasPart[]. +const maxRecentPosts = 10 + +// maxReviewedBy caps Person.reviewedBy entries. +const maxReviewedBy = 10 + +// authorFeedFetchLimit oversamples getAuthorFeed because posts_no_replies +// still returns reposts; we drop those client-side. 3x is a safe margin. +const authorFeedFetchLimit = 3 * maxRecentPosts + +// bskyPostURL returns the canonical handle-form post URL, or "" if handle +// or rkey is unusable. +func bskyPostURL(handle, rkey string) string { + if handle == "" || handle == "handle.invalid" || rkey == "" { + return "" + } + return fmt.Sprintf("https://bsky.app/profile/%s/post/%s", handle, rkey) +} + +// bskyPostURLFromATURI is bskyPostURL for callers holding an at-uri. +func bskyPostURLFromATURI(handle, atURI string) string { + parsed, err := syntax.ParseATURI(atURI) + if err != nil { + return "" + } + return bskyPostURL(handle, parsed.RecordKey().String()) +} + +// bskyPostURLFromATURIWithDIDFallback returns the handle-form post URL +// when the handle is usable, otherwise falls back to the DID-form URL +// derived from the AT-URI's authority. Returns "" only when the AT-URI is +// unparseable or has no record key. Used for nested fields (e.g. +// VideoObject.embedUrl) where omitting on handle.invalid would weaken the +// emitted structured data. +func bskyPostURLFromATURIWithDIDFallback(handle, atURI string) string { + parsed, err := syntax.ParseATURI(atURI) + if err != nil { + return "" + } + rkey := parsed.RecordKey().String() + if rkey == "" { + return "" + } + if url := bskyPostURL(handle, rkey); url != "" { + return url + } + did := parsed.Authority().String() + if did == "" { + return "" + } + return fmt.Sprintf("https://bsky.app/profile/%s/post/%s", did, rkey) +} + +// bskyProfileURL returns the canonical handle-form profile URL, or "" if +// the handle is unusable. +func bskyProfileURL(handle string) string { + if handle == "" || handle == "handle.invalid" { + return "" + } + return fmt.Sprintf("https://bsky.app/profile/%s", handle) +} + +// extractPostMedia returns thumbnail URLs for the post's image, gallery, +// or video embed, byte-identical to what we put in og:image. Callers +// derive thumbnailUrl from urls[0]. +func extractPostMedia(pv *appbsky.FeedDefs_PostView, embedHidden bool) []string { + if pv == nil || pv.Embed == nil || embedHidden { + return nil + } + + if pv.Embed.EmbedImages_View != nil { + return imageThumbs(pv.Embed.EmbedImages_View.Images) + } + if pv.Embed.EmbedGallery_View != nil { + return galleryThumbs(pv.Embed.EmbedGallery_View.Items) + } + if pv.Embed.EmbedVideo_View != nil && pv.Embed.EmbedVideo_View.Thumbnail != nil { + return []string{*pv.Embed.EmbedVideo_View.Thumbnail} + } + if pv.Embed.EmbedRecordWithMedia_View != nil && pv.Embed.EmbedRecordWithMedia_View.Media != nil { + media := pv.Embed.EmbedRecordWithMedia_View.Media + if media.EmbedImages_View != nil { + return imageThumbs(media.EmbedImages_View.Images) + } + if media.EmbedGallery_View != nil { + return galleryThumbs(media.EmbedGallery_View.Items) + } + if media.EmbedVideo_View != nil && media.EmbedVideo_View.Thumbnail != nil { + return []string{*media.EmbedVideo_View.Thumbnail} + } + } + return nil +} + +// imageThumbs returns the thumb URLs, or nil if empty. +func imageThumbs(images []*appbsky.EmbedImages_ViewImage) []string { + if len(images) == 0 { + return nil + } + urls := make([]string, 0, len(images)) + for _, img := range images { + urls = append(urls, img.Thumb) + } + return urls +} + +// galleryThumbs returns the thumbnail URLs of image items in a gallery +// embed, or nil if empty. Items_Elem is a union; non-image variants and +// nil entries are skipped so future gallery item types don't break SEO +// extraction. Empty Thumbnail strings are also skipped to avoid emitting +// if the appview ever returns one. +func galleryThumbs(items []*appbsky.EmbedGallery_View_Items_Elem) []string { + if len(items) == 0 { + return nil + } + urls := make([]string, 0, len(items)) + for _, item := range items { + if item == nil || item.EmbedGallery_ViewImage == nil { + continue + } + if item.EmbedGallery_ViewImage.Thumbnail == "" { + continue + } + urls = append(urls, item.EmbedGallery_ViewImage.Thumbnail) + } + if len(urls) == 0 { + return nil + } + return urls +} + +// findVideoEmbed returns the post's video embed view, or nil if there is +// none or embeds are hidden. Shared with extractVideoMeta so og:video and +// JSON-LD VideoObject stay in sync. +func findVideoEmbed(pv *appbsky.FeedDefs_PostView, embedHidden bool) *appbsky.EmbedVideo_View { + if pv == nil || pv.Embed == nil || embedHidden { + return nil + } + if pv.Embed.EmbedVideo_View != nil { + return pv.Embed.EmbedVideo_View + } + if pv.Embed.EmbedRecordWithMedia_View != nil && + pv.Embed.EmbedRecordWithMedia_View.Media != nil && + pv.Embed.EmbedRecordWithMedia_View.Media.EmbedVideo_View != nil { + return pv.Embed.EmbedRecordWithMedia_View.Media.EmbedVideo_View + } + return nil +} + +// buildVideoObject returns a VideoObject for the post's video embed, or +// nil if there's no usable video. Falls back to "Video by @" when +// alt text is empty so name is always populated (Google requires it). +// description falls back to name for the same reason. +func buildVideoObject(pv *appbsky.FeedDefs_PostView, embedURL, postText string, embedHidden bool) *videoObject { + v := findVideoEmbed(pv, embedHidden) + if v == nil || v.Playlist == "" { + return nil + } + vo := &videoObject{ + Type: "VideoObject", + ContentURL: v.Playlist, + EmbedURL: embedURL, + // uploadDate uses IndexedAt (not record CreatedAt) for consistency + // with DiscussionForumPosting.datePublished on the parent post. + UploadDate: pv.IndexedAt, + } + if v.Thumbnail != nil { + vo.ThumbnailURL = *v.Thumbnail + } + switch { + case v.Alt != nil && *v.Alt != "": + vo.Name = *v.Alt + case pv.Author != nil && pv.Author.Handle != "" && pv.Author.Handle != "handle.invalid": + vo.Name = "Video by @" + pv.Author.Handle + default: + vo.Name = "Video on Bluesky" + } + if postText != "" { + vo.Description = postText + } else { + vo.Description = vo.Name + } + if v.AspectRatio != nil { + vo.Width = v.AspectRatio.Width + vo.Height = v.AspectRatio.Height + } + return vo +} + +// extractQuotedPostURL returns the canonical URL of a quoted post, or "" +// if the embed is blocked / not-found / detached / a non-post record. +func extractQuotedPostURL(pv *appbsky.FeedDefs_PostView) string { + if pv == nil || pv.Embed == nil { + return "" + } + var rec *appbsky.EmbedRecord_View + if pv.Embed.EmbedRecord_View != nil { + rec = pv.Embed.EmbedRecord_View + } else if pv.Embed.EmbedRecordWithMedia_View != nil { + rec = pv.Embed.EmbedRecordWithMedia_View.Record + } + if rec == nil || rec.Record == nil { + return "" + } + vr := rec.Record.EmbedRecord_ViewRecord + if vr == nil || vr.Author == nil { + return "" + } + return bskyPostURLFromATURI(vr.Author.Handle, vr.Uri) +} + +// extractSharedContentURL returns the URL of an external link embed (also +// from the media slot of a record-with-media embed). +func extractSharedContentURL(pv *appbsky.FeedDefs_PostView) string { + if pv == nil || pv.Embed == nil { + return "" + } + if pv.Embed.EmbedExternal_View != nil && pv.Embed.EmbedExternal_View.External != nil { + return pv.Embed.EmbedExternal_View.External.Uri + } + if pv.Embed.EmbedRecordWithMedia_View != nil && + pv.Embed.EmbedRecordWithMedia_View.Media != nil && + pv.Embed.EmbedRecordWithMedia_View.Media.EmbedExternal_View != nil && + pv.Embed.EmbedRecordWithMedia_View.Media.EmbedExternal_View.External != nil { + return pv.Embed.EmbedRecordWithMedia_View.Media.EmbedExternal_View.External.Uri + } + return "" +} + +// buildAuthor constructs a Person. Organization classification for +// custom-domain accounts is a future enhancement. +func buildAuthor(author *appbsky.ActorDefs_ProfileViewBasic) *personOrOrg { + if author == nil { + return nil + } + p := &personOrOrg{ + Type: "Person", + } + if author.Did != "" { + p.Identifier = author.Did + } + if author.DisplayName != nil && *author.DisplayName != "" { + p.Name = *author.DisplayName + if author.Handle != "" { + p.AlternateName = "@" + author.Handle + } + } else if author.Handle != "" { + p.Name = "@" + author.Handle + } + if url := bskyProfileURL(author.Handle); url != "" { + p.URL = url + } + return p +} + +// buildReviewedBy maps a profile's verifications to Person entries for +// Person.reviewedBy. Skips !IsValid and missing Issuer; caps at +// maxReviewedBy. URL falls back to DID-form when the verifier's handle is +// missing or "handle.invalid". +func buildReviewedBy(state *appbsky.ActorDefs_VerificationState) []*verifier { + if state == nil || len(state.Verifications) == 0 { + return nil + } + var out []*verifier + for _, v := range state.Verifications { + if v == nil || !v.IsValid || v.Issuer == "" { + continue + } + if len(out) >= maxReviewedBy { + break + } + entry := &verifier{ + Type: "Person", + Identifier: v.Issuer, + } + var handle string + if v.IssuerHandle != nil { + handle = *v.IssuerHandle + } + if v.IssuerDisplayName != nil && *v.IssuerDisplayName != "" { + entry.Name = *v.IssuerDisplayName + if handle != "" && handle != "handle.invalid" { + entry.AlternateName = "@" + handle + } + } else if handle != "" && handle != "handle.invalid" { + entry.Name = "@" + handle + } + if url := bskyProfileURL(handle); url != "" { + entry.URL = url + } else { + entry.URL = "https://bsky.app/profile/" + v.Issuer + } + out = append(out, entry) + } + return out +} + +// postHasHideLabel reports whether the post-view or self-labels include +// any non-negated label in labelSet. Self-labels have no negation. +func postHasHideLabel(pv *appbsky.FeedDefs_PostView, labelSet map[string]bool) bool { + if pv == nil { + return false + } + for _, label := range pv.Labels { + isNeg := label.Neg != nil && *label.Neg + if labelSet[label.Val] && !isNeg { + return true + } + } + if pv.Record != nil { + if rec, ok := pv.Record.Val.(*appbsky.FeedPost); ok { + if rec.Labels != nil && rec.Labels.LabelDefs_SelfLabels != nil { + for _, label := range rec.Labels.LabelDefs_SelfLabels.Values { + if labelSet[label.Val] { + return true + } + } + } + } + } + return false +} + +// postEmbedHidden reports whether any post-view label or self-label asks +// embeds to be omitted. WebPost uses this so og:image and JSON-LD media +// suppression stay in sync. +func postEmbedHidden(pv *appbsky.FeedDefs_PostView, hideLabels map[string]bool) bool { + return postHasHideLabel(pv, hideLabels) +} + +// postRecordText returns the post's expanded text, or "" if the record is +// missing or malformed. +func postRecordText(pv *appbsky.FeedDefs_PostView) string { + if pv == nil || pv.Record == nil { + return "" + } + rec, ok := pv.Record.Val.(*appbsky.FeedPost) + if !ok { + return "" + } + return ExpandPostText(rec) +} + +// buildPostStats returns the like / comment / share interaction triple. +// commentCount is emitted separately on DiscussionForumPosting. +func buildPostStats(pv *appbsky.FeedDefs_PostView) []interactionStat { + if pv == nil { + return nil + } + deref := func(p *int64) int64 { + if p == nil { + return 0 + } + return *p + } + return []interactionStat{ + {Type: "InteractionCounter", InteractionType: "https://schema.org/LikeAction", UserInteractionCount: deref(pv.LikeCount)}, + {Type: "InteractionCounter", InteractionType: "https://schema.org/CommentAction", UserInteractionCount: deref(pv.ReplyCount)}, + {Type: "InteractionCounter", InteractionType: "https://schema.org/ShareAction", UserInteractionCount: deref(pv.RepostCount) + deref(pv.QuoteCount)}, + } +} + +// buildPostNode constructs a DiscussionForumPosting in nested form (no +// envelope, no @context). Used for top-level posts and for entries in +// hasPart / comment arrays. Returns the zero value if pv or pv.Author is +// nil; callers should treat that as "skip". Replies whose own labels match +// hideLabels or hideReplyLabels are dropped from comment[]. +func buildPostNode(pv *appbsky.FeedDefs_PostView, replies []*appbsky.FeedDefs_ThreadViewPost_Replies_Elem, hideLabels, hideReplyLabels map[string]bool) discussionForumPosting { + if pv == nil || pv.Author == nil { + return discussionForumPosting{} + } + embedHidden := postEmbedHidden(pv, hideLabels) + images := extractPostMedia(pv, embedHidden) + var thumb string + if len(images) > 0 { + thumb = images[0] + } + + postURL := bskyPostURLFromATURIWithDIDFallback(pv.Author.Handle, pv.Uri) + postText := postRecordText(pv) + + node := discussionForumPosting{ + Type: "DiscussionForumPosting", + URL: postURL, + Identifier: pv.Uri, + Author: buildAuthor(pv.Author), + Text: postText, + Image: images, + ThumbnailURL: thumb, + Video: buildVideoObject(pv, postURL, postText, embedHidden), + DatePublished: pv.IndexedAt, + InteractionStat: buildPostStats(pv), + } + + // Surface verifications on the post author only; replies do not. + if node.Author != nil { + if rb := buildReviewedBy(pv.Author.Verification); len(rb) > 0 { + node.Author.ReviewedBy = rb + } + } + + if pv.ReplyCount != nil { + node.CommentCount = pv.ReplyCount + } else { + zero := int64(0) + node.CommentCount = &zero + } + + if !embedHidden { + if quoted := extractQuotedPostURL(pv); quoted != "" { + node.IsBasedOn = quoted + } + if shared := extractSharedContentURL(pv); shared != "" { + node.SharedContent = &sharedContent{Type: "WebPage", URL: shared} + } + } + + for _, r := range replies { + if r == nil || r.FeedDefs_ThreadViewPost == nil || r.FeedDefs_ThreadViewPost.Post == nil { + continue + } + if len(node.Comment) >= maxComments { + break + } + replyPV := r.FeedDefs_ThreadViewPost.Post + // Drop labeled replies entirely so abusive/spam text isn't surfaced + // into the parent post's structured data. + if postHasHideLabel(replyPV, hideReplyLabels) || postHasHideLabel(replyPV, hideLabels) { + continue + } + reply := buildReplyNode(replyPV, hideLabels) + if reply.Type == "" { + continue + } + node.Comment = append(node.Comment, reply) + } + + return node +} + +// buildReplyNode builds a schema.org Comment for a reply. Returns the +// zero value if pv or pv.Author is nil. +func buildReplyNode(pv *appbsky.FeedDefs_PostView, hideLabels map[string]bool) comment { + if pv == nil || pv.Author == nil { + return comment{} + } + embedHidden := postEmbedHidden(pv, hideLabels) + images := extractPostMedia(pv, embedHidden) + var thumb string + if len(images) > 0 { + thumb = images[0] + } + postURL := bskyPostURLFromATURIWithDIDFallback(pv.Author.Handle, pv.Uri) + postText := postRecordText(pv) + return comment{ + Type: "Comment", + URL: postURL, + Identifier: pv.Uri, + Author: buildAuthor(pv.Author), + Text: postText, + Image: images, + ThumbnailURL: thumb, + Video: buildVideoObject(pv, postURL, postText, embedHidden), + DatePublished: pv.IndexedAt, + } +} + +// buildPostJSONLD marshals the WebPage envelope wrapping a +// DiscussionForumPosting. canonicalURL is used for both envelope.url and +// (as a fallback) mainEntity.url so they always agree. +func buildPostJSONLD(pv *appbsky.FeedDefs_PostView, replies []*appbsky.FeedDefs_ThreadViewPost_Replies_Elem, canonicalURL string, hideLabels, hideReplyLabels map[string]bool) (string, error) { + if pv == nil || pv.Author == nil { + return "", fmt.Errorf("nil post view or author") + } + node := buildPostNode(pv, replies, hideLabels, hideReplyLabels) + + // mainEntity.url is empty when the author handle is unusable; fall back + // to canonicalURL so it agrees with envelope.url. + if node.URL == "" { + node.URL = canonicalURL + } + + envelope := webPage{ + Context: schemaOrgContext, + Type: "WebPage", + URL: canonicalURL, + MainEntity: node, + } + b, err := json.Marshal(envelope) + if err != nil { + return "", err + } + return string(b), nil +} + +// buildProfileJSONLD marshals a ProfilePage (with hasPart recent posts). +// Recent posts whose own labels match hideLabels or hideReplyLabels are +// dropped from hasPart, mirroring the gating applied to comment[]. +func buildProfileJSONLD(pv *appbsky.ActorDefs_ProfileViewDetailed, recentPosts []*appbsky.FeedDefs_PostView, hideLabels, hideReplyLabels map[string]bool) (string, error) { + if pv == nil { + return "", fmt.Errorf("nil profile view") + } + + person := &personOrOrg{ + Type: "Person", + Identifier: pv.Did, + } + if pv.DisplayName != nil && *pv.DisplayName != "" { + person.Name = *pv.DisplayName + person.AlternateName = "@" + pv.Handle + } else { + person.Name = "@" + pv.Handle + } + if pv.Description != nil { + person.Description = *pv.Description + } + if pv.Avatar != nil { + person.Image = *pv.Avatar + } + + deref := func(p *int64) int64 { + if p == nil { + return 0 + } + return *p + } + person.InteractionStat = []interactionStat{ + {Type: "InteractionCounter", InteractionType: "https://schema.org/FollowAction", UserInteractionCount: deref(pv.FollowersCount)}, + } + person.AgentInteractionStat = []interactionStat{ + {Type: "InteractionCounter", InteractionType: "https://schema.org/FollowAction", UserInteractionCount: deref(pv.FollowsCount)}, + {Type: "InteractionCounter", InteractionType: "https://schema.org/WriteAction", UserInteractionCount: deref(pv.PostsCount)}, + } + if rb := buildReviewedBy(pv.Verification); len(rb) > 0 { + person.ReviewedBy = rb + } + + page := profilePage{ + Context: schemaOrgContext, + Type: "ProfilePage", + MainEntity: person, + } + if pv.CreatedAt != nil { + page.DateCreated = *pv.CreatedAt + } + + for _, rp := range recentPosts { + if rp == nil { + continue + } + if len(page.HasPart) >= maxRecentPosts { + break + } + // Drop labeled posts entirely so flagged content isn't surfaced + // into the profile's structured data. + if postHasHideLabel(rp, hideReplyLabels) || postHasHideLabel(rp, hideLabels) { + continue + } + // Recent posts go in nested form. No replies are passed, so the + // reply-label set is irrelevant; pass nil. + node := buildPostNode(rp, nil, hideLabels, nil) + if node.Type == "" { + continue + } + page.HasPart = append(page.HasPart, node) + } + + b, err := json.Marshal(page) + if err != nil { + return "", err + } + return string(b), nil +} diff --git a/bskyweb/cmd/bskyweb/jsonld_test.go b/bskyweb/cmd/bskyweb/jsonld_test.go new file mode 100644 index 0000000000..ee44008030 --- /dev/null +++ b/bskyweb/cmd/bskyweb/jsonld_test.go @@ -0,0 +1,1542 @@ +package main + +import ( + "encoding/json" + "fmt" + "strings" + "testing" + + comatprototypes "github.com/bluesky-social/indigo/api/atproto" + appbsky "github.com/bluesky-social/indigo/api/bsky" + lexutil "github.com/bluesky-social/indigo/lex/util" +) + +// Pointer helpers for optional appbsky fields. +func strPtr(s string) *string { return &s } +func intPtr(i int64) *int64 { return &i } + +// newProfileViewDetailed returns a populated profile for tests. +func newProfileViewDetailed() *appbsky.ActorDefs_ProfileViewDetailed { + return &appbsky.ActorDefs_ProfileViewDetailed{ + Did: "did:plc:alice", + Handle: "alice.bsky.social", + DisplayName: strPtr("Alice"), + Description: strPtr("just a person"), + Avatar: strPtr("https://cdn.bsky.app/img/avatar/plain/a@jpeg"), + FollowersCount: intPtr(100), + FollowsCount: intPtr(50), + PostsCount: intPtr(200), + CreatedAt: strPtr("2023-01-01T00:00:00Z"), + } +} + +// makePostView builds a minimal valid post view for tests. +func makePostView(handle, did, rkey, text string, opts ...func(*appbsky.FeedDefs_PostView)) *appbsky.FeedDefs_PostView { + uri := "at://" + did + "/app.bsky.feed.post/" + rkey + pv := &appbsky.FeedDefs_PostView{ + Uri: uri, + Cid: "bafy-test", + IndexedAt: "2024-01-02T03:04:05Z", + LikeCount: intPtr(7), + ReplyCount: intPtr(3), + RepostCount: intPtr(2), + QuoteCount: intPtr(1), + Author: &appbsky.ActorDefs_ProfileViewBasic{ + Did: did, + Handle: handle, + DisplayName: strPtr("Test User"), + }, + Record: &lexutil.LexiconTypeDecoder{ + Val: &appbsky.FeedPost{ + Text: text, + CreatedAt: "2024-01-02T03:04:05Z", + }, + }, + } + for _, opt := range opts { + opt(pv) + } + return pv +} + +// withImages adds a simple images embed. +func withImages(thumbs ...string) func(*appbsky.FeedDefs_PostView) { + return func(pv *appbsky.FeedDefs_PostView) { + var images []*appbsky.EmbedImages_ViewImage + for _, t := range thumbs { + images = append(images, &appbsky.EmbedImages_ViewImage{Thumb: t, Fullsize: t + "_full"}) + } + pv.Embed = &appbsky.FeedDefs_PostView_Embed{ + EmbedImages_View: &appbsky.EmbedImages_View{Images: images}, + } + } +} + +// withGallery adds an app.bsky.embed.gallery view with image items. +func withGallery(thumbs ...string) func(*appbsky.FeedDefs_PostView) { + return func(pv *appbsky.FeedDefs_PostView) { + var items []*appbsky.EmbedGallery_View_Items_Elem + for _, t := range thumbs { + items = append(items, &appbsky.EmbedGallery_View_Items_Elem{ + EmbedGallery_ViewImage: &appbsky.EmbedGallery_ViewImage{ + Thumbnail: t, + Fullsize: t + "_full", + }, + }) + } + pv.Embed = &appbsky.FeedDefs_PostView_Embed{ + EmbedGallery_View: &appbsky.EmbedGallery_View{Items: items}, + } + } +} + +// withRecordWithMediaGallery adds a record-with-media embed whose media slot +// is an app.bsky.embed.gallery view. +func withRecordWithMediaGallery(qHandle, qDid, qRkey string, thumbs ...string) func(*appbsky.FeedDefs_PostView) { + return func(pv *appbsky.FeedDefs_PostView) { + var items []*appbsky.EmbedGallery_View_Items_Elem + for _, t := range thumbs { + items = append(items, &appbsky.EmbedGallery_View_Items_Elem{ + EmbedGallery_ViewImage: &appbsky.EmbedGallery_ViewImage{ + Thumbnail: t, + Fullsize: t + "_full", + }, + }) + } + pv.Embed = &appbsky.FeedDefs_PostView_Embed{ + EmbedRecordWithMedia_View: &appbsky.EmbedRecordWithMedia_View{ + Record: &appbsky.EmbedRecord_View{ + Record: &appbsky.EmbedRecord_View_Record{ + EmbedRecord_ViewRecord: &appbsky.EmbedRecord_ViewRecord{ + Uri: "at://" + qDid + "/app.bsky.feed.post/" + qRkey, + Cid: "bafy-quoted", + Author: &appbsky.ActorDefs_ProfileViewBasic{ + Did: qDid, + Handle: qHandle, + }, + IndexedAt: "2024-01-01T00:00:00Z", + }, + }, + }, + Media: &appbsky.EmbedRecordWithMedia_View_Media{ + EmbedGallery_View: &appbsky.EmbedGallery_View{Items: items}, + }, + }, + } + } +} + +// withVideo adds a video embed with a thumbnail. +func withVideo(thumb string) func(*appbsky.FeedDefs_PostView) { + return func(pv *appbsky.FeedDefs_PostView) { + pv.Embed = &appbsky.FeedDefs_PostView_Embed{ + EmbedVideo_View: &appbsky.EmbedVideo_View{Thumbnail: strPtr(thumb)}, + } + } +} + +// withExternalEmbed adds an external link embed. +func withExternalEmbed(uri, title string) func(*appbsky.FeedDefs_PostView) { + return func(pv *appbsky.FeedDefs_PostView) { + pv.Embed = &appbsky.FeedDefs_PostView_Embed{ + EmbedExternal_View: &appbsky.EmbedExternal_View{ + External: &appbsky.EmbedExternal_ViewExternal{Uri: uri, Title: title, Description: "desc"}, + }, + } + } +} + +// withQuotePost adds a record (quote-post) embed of the given target post. +func withQuotePost(qHandle, qDid, qRkey string) func(*appbsky.FeedDefs_PostView) { + return func(pv *appbsky.FeedDefs_PostView) { + pv.Embed = &appbsky.FeedDefs_PostView_Embed{ + EmbedRecord_View: &appbsky.EmbedRecord_View{ + Record: &appbsky.EmbedRecord_View_Record{ + EmbedRecord_ViewRecord: &appbsky.EmbedRecord_ViewRecord{ + Uri: "at://" + qDid + "/app.bsky.feed.post/" + qRkey, + Cid: "bafy-quoted", + Author: &appbsky.ActorDefs_ProfileViewBasic{ + Did: qDid, + Handle: qHandle, + }, + IndexedAt: "2024-01-01T00:00:00Z", + }, + }, + }, + } + } +} + +// withQuotePostBlocked adds a blocked-record embed (should NOT produce isBasedOn). +func withQuotePostBlocked() func(*appbsky.FeedDefs_PostView) { + return func(pv *appbsky.FeedDefs_PostView) { + pv.Embed = &appbsky.FeedDefs_PostView_Embed{ + EmbedRecord_View: &appbsky.EmbedRecord_View{ + Record: &appbsky.EmbedRecord_View_Record{ + EmbedRecord_ViewBlocked: &appbsky.EmbedRecord_ViewBlocked{ + Uri: "at://did:plc:blocked/app.bsky.feed.post/x", + Blocked: true, + }, + }, + }, + } + } +} + +// withSelfLabel adds a self-label that should hide embeds. +func withSelfLabel(val string) func(*appbsky.FeedDefs_PostView) { + return func(pv *appbsky.FeedDefs_PostView) { + rec, _ := pv.Record.Val.(*appbsky.FeedPost) + rec.Labels = &appbsky.FeedPost_Labels{ + LabelDefs_SelfLabels: &comatprototypes.LabelDefs_SelfLabels{ + Values: []*comatprototypes.LabelDefs_SelfLabel{{Val: val}}, + }, + } + } +} + +// withPostLabel adds a post-view label (as if applied by a labeler) with an +// optional negation flag. +func withPostLabel(val string, neg bool) func(*appbsky.FeedDefs_PostView) { + return func(pv *appbsky.FeedDefs_PostView) { + label := &comatprototypes.LabelDefs_Label{Val: val, Src: "did:plc:labeler"} + if neg { + n := true + label.Neg = &n + } + pv.Labels = append(pv.Labels, label) + } +} + +// verifierSpec is a compact specifier for a verification entry in fixtures. +type verifierSpec struct { + issuer, handle, displayName string + isValid bool +} + +// makeVerificationState builds a VerificationState from the supplied specs. +func makeVerificationState(specs ...verifierSpec) *appbsky.ActorDefs_VerificationState { + state := &appbsky.ActorDefs_VerificationState{ + VerifiedStatus: "valid", + TrustedVerifierStatus: "none", + } + for _, s := range specs { + v := &appbsky.ActorDefs_VerificationView{ + Issuer: s.issuer, + IsValid: s.isValid, + CreatedAt: "2024-01-01T00:00:00Z", + Uri: "at://" + s.issuer + "/app.bsky.graph.verification/" + s.issuer, + } + if s.handle != "" { + h := s.handle + v.IssuerHandle = &h + } + if s.displayName != "" { + d := s.displayName + v.IssuerDisplayName = &d + } + state.Verifications = append(state.Verifications, v) + } + return state +} + +// withVerifications sets pv.Author.Verification. +func withVerifications(state *appbsky.ActorDefs_VerificationState) func(*appbsky.FeedDefs_PostView) { + return func(pv *appbsky.FeedDefs_PostView) { + if pv.Author == nil { + return + } + pv.Author.Verification = state + } +} + +// unmarshalLD parses the JSON-LD blob produced by buildPostJSONLD. +func unmarshalLD(t *testing.T, s string) map[string]any { + t.Helper() + var out map[string]any + if err := json.Unmarshal([]byte(s), &out); err != nil { + t.Fatalf("invalid JSON-LD: %v\n%s", err, s) + } + return out +} + +func TestBuildPostJSONLD_Bare(t *testing.T) { + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "hello") + canonical := "https://bsky.app/profile/alice.bsky.social/post/abc123" + out, err := buildPostJSONLD(pv, nil, canonical, hideEmbedLabels, hideReplyLabels) + if err != nil { + t.Fatal(err) + } + envelope := unmarshalLD(t, out) + if envelope["@context"] != "https://schema.org" { + t.Errorf("@context wrong: %v", envelope["@context"]) + } + if envelope["@type"] != "WebPage" { + t.Errorf("@type should be WebPage, got %v", envelope["@type"]) + } + if envelope["url"] != canonical { + t.Errorf("url should be canonical, got %v", envelope["url"]) + } + main, ok := envelope["mainEntity"].(map[string]any) + if !ok { + t.Fatalf("mainEntity missing") + } + if main["@type"] != "DiscussionForumPosting" { + t.Errorf("mainEntity @type wrong: %v", main["@type"]) + } + // nested entity should NOT have @context (Google's preferred shape). + if _, present := main["@context"]; present { + t.Errorf("nested mainEntity should not have @context") + } + if main["url"] != canonical { + t.Errorf("post url wrong: %v", main["url"]) + } + if main["identifier"] != pv.Uri { + t.Errorf("identifier should be at-uri: %v", main["identifier"]) + } + if main["text"] != "hello" { + t.Errorf("text wrong: %v", main["text"]) + } + if main["datePublished"] != "2024-01-02T03:04:05Z" { + t.Errorf("datePublished wrong: %v", main["datePublished"]) + } + // commentCount should always be emitted, even at zero. + cc, ok := main["commentCount"].(float64) + if !ok || int64(cc) != 3 { + t.Errorf("commentCount wrong: %v", main["commentCount"]) + } + // Author identifier should be the author's DID so a handle change + // doesn't break identity. + bareAuthor, _ := main["author"].(map[string]any) + if bareAuthor == nil { + t.Fatalf("author missing") + } + if bareAuthor["identifier"] != "did:plc:alice" { + t.Errorf("author identifier should be DID, got %v", bareAuthor["identifier"]) + } + // no images on bare post + if _, present := main["image"]; present { + t.Errorf("bare post should not have image") + } + if _, present := main["thumbnailUrl"]; present { + t.Errorf("bare post should not have thumbnailUrl") + } + if _, present := main["isBasedOn"]; present { + t.Errorf("bare post should not have isBasedOn") + } + if _, present := main["sharedContent"]; present { + t.Errorf("bare post should not have sharedContent") + } +} + +func TestBuildPostJSONLD_WithImages(t *testing.T) { + thumb1 := "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:alice/abc@jpeg" + thumb2 := "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:alice/def@jpeg" + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "look", withImages(thumb1, thumb2)) + out, err := buildPostJSONLD(pv, nil, "https://bsky.app/profile/alice.bsky.social/post/abc123", hideEmbedLabels, hideReplyLabels) + if err != nil { + t.Fatal(err) + } + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + imgs, ok := main["image"].([]any) + if !ok { + t.Fatalf("image should be array, got %T", main["image"]) + } + if len(imgs) != 2 { + t.Errorf("expected 2 images, got %d", len(imgs)) + } + if imgs[0] != thumb1 { + t.Errorf("image[0] should equal first thumb, got %v", imgs[0]) + } + if main["thumbnailUrl"] != thumb1 { + t.Errorf("thumbnailUrl should equal image[0] (Google byte-equality requirement), got %v", main["thumbnailUrl"]) + } +} + +func TestBuildPostJSONLD_WithGallery(t *testing.T) { + thumb1 := "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:alice/g1@jpeg" + thumb2 := "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:alice/g2@jpeg" + thumb3 := "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:alice/g3@jpeg" + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "gallery", withGallery(thumb1, thumb2, thumb3)) + out, err := buildPostJSONLD(pv, nil, "https://bsky.app/profile/alice.bsky.social/post/abc123", hideEmbedLabels, hideReplyLabels) + if err != nil { + t.Fatal(err) + } + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + imgs, ok := main["image"].([]any) + if !ok { + t.Fatalf("image should be array, got %T", main["image"]) + } + if len(imgs) != 3 { + t.Errorf("expected 3 gallery images, got %d", len(imgs)) + } + if imgs[0] != thumb1 || imgs[1] != thumb2 || imgs[2] != thumb3 { + t.Errorf("gallery image[] order wrong: %v", imgs) + } + if main["thumbnailUrl"] != thumb1 { + t.Errorf("thumbnailUrl should equal image[0] (Google byte-equality requirement), got %v", main["thumbnailUrl"]) + } +} + +// Gallery in the media slot of a record-with-media embed should still +// produce og:image / JSON-LD image[]. Quote-post URL still emits +// alongside via isBasedOn. +func TestBuildPostJSONLD_GalleryInRecordWithMedia(t *testing.T) { + thumb := "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:alice/g@jpeg" + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "quote+gallery", + withRecordWithMediaGallery("bob.example.com", "did:plc:bob", "xyz", thumb)) + out, err := buildPostJSONLD(pv, nil, "u", hideEmbedLabels, hideReplyLabels) + if err != nil { + t.Fatal(err) + } + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + imgs, ok := main["image"].([]any) + if !ok || len(imgs) != 1 || imgs[0] != thumb { + t.Errorf("expected single gallery thumb in image[], got %v", main["image"]) + } + if main["thumbnailUrl"] != thumb { + t.Errorf("thumbnailUrl wrong: %v", main["thumbnailUrl"]) + } + if main["isBasedOn"] != "https://bsky.app/profile/bob.example.com/post/xyz" { + t.Errorf("isBasedOn should still emit for record-with-media gallery, got %v", main["isBasedOn"]) + } +} + +// Forward-compat: nil items, unknown-variant union elements, and empty +// Thumbnail strings must be skipped, not panic or leak as . Unknown variants are dropped silently +// so older deploys keep working when new gallery item types ship. +func TestExtractPostMedia_GallerySkipsUnknownItems(t *testing.T) { + thumb := "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:alice/g@jpeg" + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "gallery") + pv.Embed = &appbsky.FeedDefs_PostView_Embed{ + EmbedGallery_View: &appbsky.EmbedGallery_View{ + Items: []*appbsky.EmbedGallery_View_Items_Elem{ + nil, + {}, // empty union, no variant set + {EmbedGallery_ViewImage: &appbsky.EmbedGallery_ViewImage{Thumbnail: ""}}, // empty Thumbnail + {EmbedGallery_ViewImage: &appbsky.EmbedGallery_ViewImage{Thumbnail: thumb}}, + }, + }, + } + got := extractPostMedia(pv, false) + if len(got) != 1 || got[0] != thumb { + t.Errorf("expected single thumb, got %v", got) + } + + // All-nil / all-unknown gallery should produce no thumbs (not [""]). + pv.Embed = &appbsky.FeedDefs_PostView_Embed{ + EmbedGallery_View: &appbsky.EmbedGallery_View{ + Items: []*appbsky.EmbedGallery_View_Items_Elem{nil, {}}, + }, + } + if got := extractPostMedia(pv, false); got != nil { + t.Errorf("expected nil for empty/unknown-only gallery, got %v", got) + } +} + +func TestBuildPostJSONLD_WithVideo(t *testing.T) { + thumb := "https://cdn.bsky.app/img/video_thumbnail/plain/did:plc:alice/v@jpeg" + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "watch", withVideo(thumb)) + out, _ := buildPostJSONLD(pv, nil, "u", hideEmbedLabels, hideReplyLabels) + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + if main["thumbnailUrl"] != thumb { + t.Errorf("video thumbnailUrl wrong: %v", main["thumbnailUrl"]) + } + imgs := main["image"].([]any) + if len(imgs) != 1 || imgs[0] != thumb { + t.Errorf("image[0] should be video thumb, got %v", imgs) + } +} + +func TestBuildPostJSONLD_QuotePost(t *testing.T) { + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "quoting!", withQuotePost("bob.example.com", "did:plc:bob", "xyz")) + out, _ := buildPostJSONLD(pv, nil, "u", hideEmbedLabels, hideReplyLabels) + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + if main["isBasedOn"] != "https://bsky.app/profile/bob.example.com/post/xyz" { + t.Errorf("isBasedOn wrong: %v", main["isBasedOn"]) + } +} + +func TestBuildPostJSONLD_QuoteBlocked(t *testing.T) { + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "quoting blocked", withQuotePostBlocked()) + out, _ := buildPostJSONLD(pv, nil, "u", hideEmbedLabels, hideReplyLabels) + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + if _, present := main["isBasedOn"]; present { + t.Errorf("blocked quote should not produce isBasedOn") + } +} + +func TestBuildPostJSONLD_ExternalEmbed(t *testing.T) { + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "check this out", withExternalEmbed("https://www.spiegel.de/article", "Title")) + out, _ := buildPostJSONLD(pv, nil, "u", hideEmbedLabels, hideReplyLabels) + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + sc, ok := main["sharedContent"].(map[string]any) + if !ok { + t.Fatalf("sharedContent missing") + } + if sc["@type"] != "WebPage" { + t.Errorf("sharedContent type wrong: %v", sc["@type"]) + } + if sc["url"] != "https://www.spiegel.de/article" { + t.Errorf("sharedContent url wrong: %v", sc["url"]) + } +} + +func TestBuildPostJSONLD_HiddenEmbed(t *testing.T) { + thumb := "https://cdn.bsky.app/img/x@jpeg" + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "nsfw", + withImages(thumb), withSelfLabel("porn")) + out, _ := buildPostJSONLD(pv, nil, "u", hideEmbedLabels, hideReplyLabels) + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + if _, present := main["image"]; present { + t.Errorf("hidden-embed post should not emit image") + } + if _, present := main["thumbnailUrl"]; present { + t.Errorf("hidden-embed post should not emit thumbnailUrl") + } +} + +// Symmetric guard for the gallery extraction path. Functionally redundant +// with the early-return at the top of extractPostMedia, but exists so the +// hide-embed contract is asserted directly against the gallery branch - +// catches anyone who later moves the embedHidden check inside an +// embed-shape branch. +func TestBuildPostJSONLD_HiddenEmbed_Gallery(t *testing.T) { + thumb := "https://cdn.bsky.app/img/g@jpeg" + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "nsfw", + withGallery(thumb), withSelfLabel("porn")) + out, _ := buildPostJSONLD(pv, nil, "u", hideEmbedLabels, hideReplyLabels) + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + if _, present := main["image"]; present { + t.Errorf("hidden-embed gallery post should not emit image") + } + if _, present := main["thumbnailUrl"]; present { + t.Errorf("hidden-embed gallery post should not emit thumbnailUrl") + } +} + +func TestBuildPostJSONLD_TextEscaping(t *testing.T) { + // Includes ", \, newline, , and a unicode char. + tricky := "hello \"world\" \\ <\\>\n 🎉" + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", tricky) + out, err := buildPostJSONLD(pv, nil, "u", hideEmbedLabels, hideReplyLabels) + if err != nil { + t.Fatal(err) + } + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + if main["text"] != tricky { + t.Errorf("text round-trip failed: got %q want %q", main["text"], tricky) + } + // Literal would break out of the script tag. + if strings.Contains(out, "") { + t.Errorf("output contains literal , would break HTML embedding") + } +} + +func TestBuildPostJSONLD_Comments(t *testing.T) { + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "main") + *pv.ReplyCount = 14 + + // 12 valid replies + 1 not-found + 1 blocked. Cap is maxComments=10. + const validReplies = 12 + var replies []*appbsky.FeedDefs_ThreadViewPost_Replies_Elem + for i := 0; i < validReplies; i++ { + rkey := fmt.Sprintf("reply%02d", i) + reply := makePostView(fmt.Sprintf("rep%02d.bsky.social", i), + fmt.Sprintf("did:plc:rep%02d", i), rkey, "reply text") + replies = append(replies, &appbsky.FeedDefs_ThreadViewPost_Replies_Elem{ + FeedDefs_ThreadViewPost: &appbsky.FeedDefs_ThreadViewPost{Post: reply}, + }) + } + replies = append(replies, &appbsky.FeedDefs_ThreadViewPost_Replies_Elem{ + FeedDefs_NotFoundPost: &appbsky.FeedDefs_NotFoundPost{Uri: "at://x/y/z"}, + }) + replies = append(replies, &appbsky.FeedDefs_ThreadViewPost_Replies_Elem{ + FeedDefs_BlockedPost: &appbsky.FeedDefs_BlockedPost{Uri: "at://x/y/z"}, + }) + + out, _ := buildPostJSONLD(pv, replies, "u", hideEmbedLabels, hideReplyLabels) + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + + if cc := main["commentCount"].(float64); int64(cc) != 14 { + t.Errorf("commentCount should reflect ReplyCount, got %v", cc) + } + comments, ok := main["comment"].([]any) + if !ok { + t.Fatalf("comment array missing") + } + if len(comments) != maxComments { + t.Errorf("expected %d comments (capped from %d valid), got %d", maxComments, validReplies, len(comments)) + } + // FIFO order: first valid reply, not the not-found at the end. + first := comments[0].(map[string]any) + if first["identifier"] != "at://did:plc:rep00/app.bsky.feed.post/reply00" { + t.Errorf("first comment should be reply00, got %v", first["identifier"]) + } + // Comment type, no nested comment[] / isBasedOn / sharedContent. + for i, c := range comments { + cm := c.(map[string]any) + if cm["@type"] != "Comment" { + t.Errorf("reply %d wrong type: got %v, want Comment", i, cm["@type"]) + } + if _, present := cm["comment"]; present { + t.Errorf("reply %d should not have nested comment[]", i) + } + if _, present := cm["isBasedOn"]; present { + t.Errorf("reply %d should not have isBasedOn", i) + } + if _, present := cm["sharedContent"]; present { + t.Errorf("reply %d should not have sharedContent", i) + } + if cm["url"] == nil || cm["identifier"] == nil { + t.Errorf("reply %d missing url/identifier", i) + } + } +} + +func TestBuildPostJSONLD_HandleInvalidAuthor(t *testing.T) { + pv := makePostView("handle.invalid", "did:plc:alice", "abc123", "hello") + fallback := "https://bsky.app/profile/did:plc:alice/post/abc123" + out, _ := buildPostJSONLD(pv, nil, fallback, hideEmbedLabels, hideReplyLabels) + envelope := unmarshalLD(t, out) + main := envelope["mainEntity"].(map[string]any) + // mainEntity.url falls back to the caller's canonical URL so envelope + // and post URLs always agree. + if main["url"] != fallback { + t.Errorf("mainEntity.url should fall back to canonical URL, got %v", main["url"]) + } + if envelope["url"] != fallback { + t.Errorf("envelope.url should equal canonical URL, got %v", envelope["url"]) + } + if main["url"] != envelope["url"] { + t.Errorf("envelope.url and mainEntity.url disagree: %v vs %v", + envelope["url"], main["url"]) + } + // identifier (AT-URI) stays stable across handle changes. + if main["identifier"] != pv.Uri { + t.Errorf("identifier should still be the AT-URI, got %v", main["identifier"]) + } + // Author URL omitted (no usable handle). + author := main["author"].(map[string]any) + if _, present := author["url"]; present { + t.Errorf("handle.invalid author should not produce author.url") + } +} + +// envelope.url and mainEntity.url must always agree. +func TestBuildPostJSONLD_EnvelopeURLMatchesMainEntity(t *testing.T) { + cases := []struct { + name, handle, did, rkey, canonical string + }{ + { + name: "handle form", + handle: "alice.bsky.social", + did: "did:plc:alice", + rkey: "abc", + canonical: "https://bsky.app/profile/alice.bsky.social/post/abc", + }, + { + name: "handle.invalid falls back to canonical", + handle: "handle.invalid", + did: "did:plc:alice", + rkey: "abc", + canonical: "https://bsky.app/profile/did:plc:alice/post/abc", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + pv := makePostView(tc.handle, tc.did, tc.rkey, "hi") + out, _ := buildPostJSONLD(pv, nil, tc.canonical, hideEmbedLabels, hideReplyLabels) + env := unmarshalLD(t, out) + main := env["mainEntity"].(map[string]any) + if env["url"] != tc.canonical { + t.Errorf("envelope.url = %v, want %v", env["url"], tc.canonical) + } + if main["url"] != tc.canonical { + t.Errorf("mainEntity.url = %v, want %v", main["url"], tc.canonical) + } + }) + } +} + +func TestBuildPostJSONLD_NilAuthor(t *testing.T) { + // Defensive: don't panic if Author is nil. + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "hi") + pv.Author = nil + if _, err := buildPostJSONLD(pv, nil, "u", hideEmbedLabels, hideReplyLabels); err == nil { + t.Errorf("expected error for nil-author post, got nil") + } +} + +func TestBuildPostJSONLD_NilAuthorReply(t *testing.T) { + // Reply with nil Author should be dropped, not emitted with empty @type. + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "main") + *pv.ReplyCount = 2 + + goodReply := makePostView("bob.bsky.social", "did:plc:bob", "rep1", "good") + badReply := makePostView("eve.bsky.social", "did:plc:eve", "rep2", "bad") + badReply.Author = nil + + replies := []*appbsky.FeedDefs_ThreadViewPost_Replies_Elem{ + {FeedDefs_ThreadViewPost: &appbsky.FeedDefs_ThreadViewPost{Post: goodReply}}, + {FeedDefs_ThreadViewPost: &appbsky.FeedDefs_ThreadViewPost{Post: badReply}}, + } + out, err := buildPostJSONLD(pv, replies, "u", hideEmbedLabels, hideReplyLabels) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + comments, _ := main["comment"].([]any) + if len(comments) != 1 { + t.Errorf("expected 1 comment (nil-Author dropped), got %d", len(comments)) + } +} + +func TestBuildPostJSONLD_CommentMedia(t *testing.T) { + // Comments with media embeds should expose image[] and thumbnailUrl. + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "main") + *pv.ReplyCount = 1 + thumb := "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:bob/x@jpg" + reply := makePostView("bob.bsky.social", "did:plc:bob", "rep1", "with image", + withImages(thumb)) + replies := []*appbsky.FeedDefs_ThreadViewPost_Replies_Elem{ + {FeedDefs_ThreadViewPost: &appbsky.FeedDefs_ThreadViewPost{Post: reply}}, + } + out, _ := buildPostJSONLD(pv, replies, "u", hideEmbedLabels, hideReplyLabels) + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + c := main["comment"].([]any)[0].(map[string]any) + imgs, ok := c["image"].([]any) + if !ok || len(imgs) != 1 || imgs[0] != thumb { + t.Errorf("comment image[] wrong: %v", c["image"]) + } + if c["thumbnailUrl"] != thumb { + t.Errorf("comment thumbnailUrl wrong: %v", c["thumbnailUrl"]) + } +} + +func TestBuildProfileJSONLD_Basic(t *testing.T) { + pv := &appbsky.ActorDefs_ProfileViewDetailed{ + Did: "did:plc:alice", + Handle: "alice.bsky.social", + DisplayName: strPtr("Alice"), + Description: strPtr("just a person"), + Avatar: strPtr("https://cdn.bsky.app/img/avatar/plain/a@jpeg"), + FollowersCount: intPtr(100), + FollowsCount: intPtr(50), + PostsCount: intPtr(200), + CreatedAt: strPtr("2023-01-01T00:00:00Z"), + } + out, err := buildProfileJSONLD(pv, nil, hideEmbedLabels, hideReplyLabels) + if err != nil { + t.Fatal(err) + } + page := unmarshalLD(t, out) + if page["@context"] != "https://schema.org" { + t.Errorf("@context wrong") + } + if page["@type"] != "ProfilePage" { + t.Errorf("@type wrong: %v", page["@type"]) + } + main := page["mainEntity"].(map[string]any) + if main["@type"] != "Person" { + t.Errorf("mainEntity @type wrong: %v", main["@type"]) + } + if main["name"] != "Alice" { + t.Errorf("name wrong: %v", main["name"]) + } + if main["alternateName"] != "@alice.bsky.social" { + t.Errorf("alternateName wrong: %v", main["alternateName"]) + } + if main["identifier"] != "did:plc:alice" { + t.Errorf("identifier wrong: %v", main["identifier"]) + } + if _, present := page["hasPart"]; present { + t.Errorf("empty recentPosts should not produce hasPart") + } +} + +func TestBuildProfileJSONLD_HasPart(t *testing.T) { + pv := &appbsky.ActorDefs_ProfileViewDetailed{ + Did: "did:plc:alice", Handle: "alice.bsky.social", + DisplayName: strPtr("Alice"), + CreatedAt: strPtr("2023-01-01T00:00:00Z"), + } + var posts []*appbsky.FeedDefs_PostView + for i := 0; i < 12; i++ { + posts = append(posts, makePostView("alice.bsky.social", "did:plc:alice", + "r"+string(rune('0'+i)), "post")) + } + out, _ := buildProfileJSONLD(pv, posts, hideEmbedLabels, hideReplyLabels) + page := unmarshalLD(t, out) + hp, ok := page["hasPart"].([]any) + if !ok { + t.Fatalf("hasPart missing") + } + if len(hp) != maxRecentPosts { + t.Errorf("hasPart should be capped at %d, got %d", maxRecentPosts, len(hp)) + } + first := hp[0].(map[string]any) + if first["@type"] != "DiscussionForumPosting" { + t.Errorf("hasPart entries should be DiscussionForumPosting, got %v", first["@type"]) + } + if _, present := first["@context"]; present { + t.Errorf("nested hasPart entries should not have @context") + } +} + +func TestBuildProfileJSONLD_HasPartLabelFiltered(t *testing.T) { + // Recent posts carrying hideReplyLabels or hideEmbedLabels are dropped + // from hasPart entirely. Negation is honored on post-view labels. + pv := &appbsky.ActorDefs_ProfileViewDetailed{ + Did: "did:plc:alice", Handle: "alice.bsky.social", + DisplayName: strPtr("Alice"), + CreatedAt: strPtr("2023-01-01T00:00:00Z"), + } + good := makePostView("alice.bsky.social", "did:plc:alice", "good", "ok") + hidden := makePostView("alice.bsky.social", "did:plc:alice", "hide", "hidden", + withPostLabel("!hide", false)) + spam := makePostView("alice.bsky.social", "did:plc:alice", "spam", "spam", + withSelfLabel("spam")) + embedHide := makePostView("alice.bsky.social", "did:plc:alice", "harm", "embed-only", + withPostLabel("self-harm", false)) + negated := makePostView("alice.bsky.social", "did:plc:alice", "neg", "negated", + withPostLabel("!hide", true)) + + out, _ := buildProfileJSONLD(pv, []*appbsky.FeedDefs_PostView{ + good, hidden, spam, embedHide, negated, + }, hideEmbedLabels, hideReplyLabels) + page := unmarshalLD(t, out) + hp, _ := page["hasPart"].([]any) + + got := make(map[string]bool, len(hp)) + for _, e := range hp { + got[e.(map[string]any)["identifier"].(string)] = true + } + if !got[good.Uri] { + t.Errorf("expected unlabeled post in hasPart") + } + if !got[negated.Uri] { + t.Errorf("negated hide label should not gate; expected post in hasPart") + } + if got[hidden.Uri] { + t.Errorf("post with !hide label should be dropped from hasPart") + } + if got[spam.Uri] { + t.Errorf("self-labeled spam post should be dropped from hasPart") + } + if got[embedHide.Uri] { + t.Errorf("post with hideEmbedLabels label should be dropped from hasPart") + } +} + +func TestBskyPostURL(t *testing.T) { + tests := []struct { + name, handle, rkey, want string + }{ + {"valid", "alice.bsky.social", "abc", "https://bsky.app/profile/alice.bsky.social/post/abc"}, + {"empty handle", "", "abc", ""}, + {"handle.invalid", "handle.invalid", "abc", ""}, + {"empty rkey", "alice.bsky.social", "", ""}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := bskyPostURL(tt.handle, tt.rkey); got != tt.want { + t.Errorf("got %q, want %q", got, tt.want) + } + }) + } +} + +func TestBskyPostURLFromATURI(t *testing.T) { + tests := []struct { + name, handle, atURI, want string + }{ + {"valid", "alice.bsky.social", "at://did:plc:alice/app.bsky.feed.post/abc", "https://bsky.app/profile/alice.bsky.social/post/abc"}, + {"empty handle", "", "at://did:plc:alice/app.bsky.feed.post/abc", ""}, + {"handle.invalid", "handle.invalid", "at://did:plc:alice/app.bsky.feed.post/abc", ""}, + {"bad uri", "alice.bsky.social", "not-an-aturi", ""}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := bskyPostURLFromATURI(tt.handle, tt.atURI); got != tt.want { + t.Errorf("got %q, want %q", got, tt.want) + } + }) + } +} + +func TestBskyProfileURL(t *testing.T) { + if bskyProfileURL("alice.bsky.social") != "https://bsky.app/profile/alice.bsky.social" { + t.Errorf("valid handle wrong") + } + if bskyProfileURL("handle.invalid") != "" { + t.Errorf("handle.invalid should produce empty") + } + if bskyProfileURL("") != "" { + t.Errorf("empty handle should produce empty") + } +} + +// buildReplies wraps a slice of post views into ThreadViewPost reply elements. +func buildReplies(posts ...*appbsky.FeedDefs_PostView) []*appbsky.FeedDefs_ThreadViewPost_Replies_Elem { + out := make([]*appbsky.FeedDefs_ThreadViewPost_Replies_Elem, 0, len(posts)) + for _, p := range posts { + out = append(out, &appbsky.FeedDefs_ThreadViewPost_Replies_Elem{ + FeedDefs_ThreadViewPost: &appbsky.FeedDefs_ThreadViewPost{Post: p}, + }) + } + return out +} + +// commentIdentifiers extracts the identifier of each entry in mainEntity.comment. +func commentIdentifiers(t *testing.T, out string) []string { + t.Helper() + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + raw, _ := main["comment"].([]any) + ids := make([]string, 0, len(raw)) + for _, c := range raw { + cm := c.(map[string]any) + if id, ok := cm["identifier"].(string); ok { + ids = append(ids, id) + } + } + return ids +} + +func TestBuildPostJSONLD_HiddenReplyDropped_PostViewLabel(t *testing.T) { + // A reply carrying a hideReplyLabels post-view label is dropped from comment[]. + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "main") + good := makePostView("bob.bsky.social", "did:plc:bob", "rep1", "good reply") + bad := makePostView("eve.bsky.social", "did:plc:eve", "rep2", "spam reply", + withPostLabel("!hide", false)) + out, _ := buildPostJSONLD(pv, buildReplies(good, bad), "u", hideEmbedLabels, hideReplyLabels) + ids := commentIdentifiers(t, out) + if len(ids) != 1 || ids[0] != good.Uri { + t.Errorf("expected only the unlabeled reply to remain, got %v", ids) + } +} + +func TestBuildPostJSONLD_HiddenReplyDropped_SelfLabel(t *testing.T) { + // A reply self-labeling itself with a hideReplyLabels value is dropped. + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "main") + good := makePostView("bob.bsky.social", "did:plc:bob", "rep1", "good reply") + bad := makePostView("eve.bsky.social", "did:plc:eve", "rep2", "spam reply", + withSelfLabel("spam")) + out, _ := buildPostJSONLD(pv, buildReplies(good, bad), "u", hideEmbedLabels, hideReplyLabels) + ids := commentIdentifiers(t, out) + if len(ids) != 1 || ids[0] != good.Uri { + t.Errorf("expected self-labeled reply dropped, got %v", ids) + } +} + +func TestBuildPostJSONLD_HiddenReplyDropped_EmbedLabel(t *testing.T) { + // A reply with a hideEmbedLabels label (e.g., porn) is also dropped. + // hideEmbedLabels is consulted in addition to hideReplyLabels for replies. + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "main") + good := makePostView("bob.bsky.social", "did:plc:bob", "rep1", "good reply") + // "self-harm" is in hideEmbedLabels but not hideReplyLabels — verifies the + // union behavior. + bad := makePostView("eve.bsky.social", "did:plc:eve", "rep2", "concerning reply", + withPostLabel("self-harm", false)) + out, _ := buildPostJSONLD(pv, buildReplies(good, bad), "u", hideEmbedLabels, hideReplyLabels) + ids := commentIdentifiers(t, out) + if len(ids) != 1 || ids[0] != good.Uri { + t.Errorf("expected embed-labeled reply dropped, got %v", ids) + } +} + +func TestBuildPostJSONLD_NegatedHideLabelKept(t *testing.T) { + // A negated post-view label should not gate the reply. + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "main") + reply := makePostView("bob.bsky.social", "did:plc:bob", "rep1", "fine reply", + withPostLabel("!hide", true)) + out, _ := buildPostJSONLD(pv, buildReplies(reply), "u", hideEmbedLabels, hideReplyLabels) + ids := commentIdentifiers(t, out) + if len(ids) != 1 || ids[0] != reply.Uri { + t.Errorf("expected negated-label reply to be kept, got %v", ids) + } +} + +func TestBuildPostJSONLD_ReplyAuthorHasIdentifier(t *testing.T) { + // Reply author should also carry a DID identifier. + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "main") + reply := makePostView("bob.bsky.social", "did:plc:bob", "rep1", "hi") + out, _ := buildPostJSONLD(pv, buildReplies(reply), "u", hideEmbedLabels, hideReplyLabels) + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + c := main["comment"].([]any)[0].(map[string]any) + auth, ok := c["author"].(map[string]any) + if !ok { + t.Fatalf("comment author missing") + } + if auth["identifier"] != "did:plc:bob" { + t.Errorf("reply author identifier should be DID, got %v", auth["identifier"]) + } +} + +func TestBuildReviewedBy_NilAndEmpty(t *testing.T) { + if got := buildReviewedBy(nil); got != nil { + t.Errorf("nil state should yield nil, got %v", got) + } + empty := &appbsky.ActorDefs_VerificationState{} + if got := buildReviewedBy(empty); got != nil { + t.Errorf("empty Verifications should yield nil, got %v", got) + } + allInvalid := makeVerificationState( + verifierSpec{issuer: "did:plc:v1", handle: "v1.example.com", displayName: "V One", isValid: false}, + verifierSpec{issuer: "did:plc:v2", handle: "v2.example.com", displayName: "V Two", isValid: false}, + ) + if got := buildReviewedBy(allInvalid); got != nil { + t.Errorf("all-invalid state should yield nil, got %v", got) + } +} + +func TestBuildReviewedBy_FiltersInvalid(t *testing.T) { + state := makeVerificationState( + verifierSpec{issuer: "did:plc:v1", handle: "v1.example.com", displayName: "V One", isValid: true}, + verifierSpec{issuer: "did:plc:v2", handle: "v2.example.com", displayName: "V Two", isValid: false}, + verifierSpec{issuer: "did:plc:v3", handle: "v3.example.com", displayName: "V Three", isValid: true}, + verifierSpec{issuer: "", handle: "noid.example.com", displayName: "No Issuer", isValid: true}, + ) + got := buildReviewedBy(state) + if len(got) != 2 { + t.Fatalf("expected 2 valid verifiers, got %d: %v", len(got), got) + } + if got[0].Identifier != "did:plc:v1" { + t.Errorf("FIFO order broken; first identifier = %q", got[0].Identifier) + } + if got[1].Identifier != "did:plc:v3" { + t.Errorf("expected v3 at index 1, got %q", got[1].Identifier) + } +} + +func TestBuildReviewedBy_NameFallbacks(t *testing.T) { + cases := []struct { + name string + spec verifierSpec + wantName, wantAlternateName, wantURL, wantIdentif string + }{ + { + name: "DisplayName + handle", + spec: verifierSpec{issuer: "did:plc:v1", handle: "alice.example.com", displayName: "Alice Verifier", isValid: true}, + wantName: "Alice Verifier", + wantAlternateName: "@alice.example.com", + wantURL: "https://bsky.app/profile/alice.example.com", + wantIdentif: "did:plc:v1", + }, + { + name: "handle only", + spec: verifierSpec{issuer: "did:plc:v2", handle: "bob.example.com", isValid: true}, + wantName: "@bob.example.com", + wantAlternateName: "", + wantURL: "https://bsky.app/profile/bob.example.com", + wantIdentif: "did:plc:v2", + }, + { + name: "DisplayName only (no handle)", + spec: verifierSpec{issuer: "did:plc:v3", displayName: "Carol Verifier", isValid: true}, + wantName: "Carol Verifier", + wantAlternateName: "", + wantURL: "https://bsky.app/profile/did:plc:v3", + wantIdentif: "did:plc:v3", + }, + { + name: "DisplayName + handle.invalid", + spec: verifierSpec{issuer: "did:plc:v4", handle: "handle.invalid", displayName: "Dave Verifier", isValid: true}, + wantName: "Dave Verifier", + wantAlternateName: "", + wantURL: "https://bsky.app/profile/did:plc:v4", + wantIdentif: "did:plc:v4", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got := buildReviewedBy(makeVerificationState(tc.spec)) + if len(got) != 1 { + t.Fatalf("expected 1 entry, got %d", len(got)) + } + v := got[0] + if v.Type != "Person" { + t.Errorf("@type = %q, want Person", v.Type) + } + if v.Name != tc.wantName { + t.Errorf("name = %q, want %q", v.Name, tc.wantName) + } + if v.AlternateName != tc.wantAlternateName { + t.Errorf("alternateName = %q, want %q", v.AlternateName, tc.wantAlternateName) + } + if v.URL != tc.wantURL { + t.Errorf("url = %q, want %q", v.URL, tc.wantURL) + } + if v.Identifier != tc.wantIdentif { + t.Errorf("identifier = %q, want %q", v.Identifier, tc.wantIdentif) + } + }) + } +} + +func TestBuildReviewedBy_Cap(t *testing.T) { + specs := make([]verifierSpec, 0, 12) + for i := 0; i < 12; i++ { + specs = append(specs, verifierSpec{ + issuer: fmt.Sprintf("did:plc:v%02d", i), + handle: fmt.Sprintf("v%02d.example.com", i), + displayName: fmt.Sprintf("V%02d", i), + isValid: true, + }) + } + got := buildReviewedBy(makeVerificationState(specs...)) + if len(got) != maxReviewedBy { + t.Errorf("expected cap at %d, got %d", maxReviewedBy, len(got)) + } + if got[0].Identifier != "did:plc:v00" { + t.Errorf("first kept entry should be v00, got %q", got[0].Identifier) + } +} + +func TestBuildPostJSONLD_AuthorReviewedBy(t *testing.T) { + state := makeVerificationState(verifierSpec{ + issuer: "did:plc:verifier1", + handle: "verifier.example.com", + displayName: "Trusted Verifier", + isValid: true, + }) + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "hi", + withVerifications(state)) + out, err := buildPostJSONLD(pv, nil, "u", hideEmbedLabels, hideReplyLabels) + if err != nil { + t.Fatal(err) + } + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + auth := main["author"].(map[string]any) + rb, ok := auth["reviewedBy"].([]any) + if !ok { + t.Fatalf("post author should have reviewedBy, got %v", auth["reviewedBy"]) + } + if len(rb) != 1 { + t.Fatalf("expected 1 verifier, got %d", len(rb)) + } + v := rb[0].(map[string]any) + if v["@type"] != "Person" { + t.Errorf("verifier @type = %v, want Person", v["@type"]) + } + if v["name"] != "Trusted Verifier" { + t.Errorf("verifier name = %v", v["name"]) + } + if v["identifier"] != "did:plc:verifier1" { + t.Errorf("verifier identifier = %v", v["identifier"]) + } + if v["url"] != "https://bsky.app/profile/verifier.example.com" { + t.Errorf("verifier url = %v", v["url"]) + } +} + +func TestBuildPostJSONLD_ReplyAuthorNoReviewedBy(t *testing.T) { + // Replies do not surface verifications even when the reply author + // carries Verification. + state := makeVerificationState(verifierSpec{ + issuer: "did:plc:verifier1", + handle: "verifier.example.com", + displayName: "Trusted Verifier", + isValid: true, + }) + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "main") + reply := makePostView("bob.bsky.social", "did:plc:bob", "rep1", "hi", + withVerifications(state)) + out, _ := buildPostJSONLD(pv, buildReplies(reply), "u", hideEmbedLabels, hideReplyLabels) + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + c := main["comment"].([]any)[0].(map[string]any) + auth := c["author"].(map[string]any) + if _, present := auth["reviewedBy"]; present { + t.Errorf("reply author must not carry reviewedBy, got %v", auth["reviewedBy"]) + } +} + +func TestBuildProfileJSONLD_MainEntityReviewedBy(t *testing.T) { + pv := newProfileViewDetailed() + pv.Verification = makeVerificationState(verifierSpec{ + issuer: "did:plc:verifier1", + handle: "verifier.example.com", + displayName: "Trusted Verifier", + isValid: true, + }) + out, err := buildProfileJSONLD(pv, nil, hideEmbedLabels, hideReplyLabels) + if err != nil { + t.Fatal(err) + } + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + rb, ok := main["reviewedBy"].([]any) + if !ok { + t.Fatalf("profile mainEntity should have reviewedBy, got %v", main["reviewedBy"]) + } + v := rb[0].(map[string]any) + if v["identifier"] != "did:plc:verifier1" { + t.Errorf("verifier identifier = %v", v["identifier"]) + } + if v["url"] != "https://bsky.app/profile/verifier.example.com" { + t.Errorf("verifier url = %v", v["url"]) + } +} + +func TestBuildProfileJSONLD_HasPartAuthorReviewedBy(t *testing.T) { + // Recent posts inherit verifications via their post-view Author. + pv := newProfileViewDetailed() + state := makeVerificationState(verifierSpec{ + issuer: "did:plc:verifier1", + handle: "verifier.example.com", + displayName: "Trusted Verifier", + isValid: true, + }) + post := makePostView("alice.bsky.social", "did:plc:alice", "rp1", "hi", + withVerifications(state)) + out, _ := buildProfileJSONLD(pv, []*appbsky.FeedDefs_PostView{post}, hideEmbedLabels, hideReplyLabels) + page := unmarshalLD(t, out) + hp := page["hasPart"].([]any) + if len(hp) != 1 { + t.Fatalf("expected 1 hasPart entry, got %d", len(hp)) + } + auth := hp[0].(map[string]any)["author"].(map[string]any) + rb, ok := auth["reviewedBy"].([]any) + if !ok || len(rb) != 1 { + t.Fatalf("hasPart author should carry reviewedBy, got %v", auth["reviewedBy"]) + } + if rb[0].(map[string]any)["identifier"] != "did:plc:verifier1" { + t.Errorf("verifier identifier wrong: %v", rb[0]) + } +} + +// videoEmbedOpts configures withVideoFull. Zero values mean "not set". +type videoEmbedOpts struct { + thumbnail string + playlist string + alt string + width int64 + height int64 + hasAspect bool + recordMedia bool // nest under EmbedRecordWithMedia_View.Media +} + +// withVideoFull installs a fully-specified video embed for VideoObject tests. +func withVideoFull(o videoEmbedOpts) func(*appbsky.FeedDefs_PostView) { + return func(pv *appbsky.FeedDefs_PostView) { + v := &appbsky.EmbedVideo_View{Playlist: o.playlist} + if o.thumbnail != "" { + v.Thumbnail = strPtr(o.thumbnail) + } + if o.alt != "" { + v.Alt = strPtr(o.alt) + } + if o.hasAspect { + v.AspectRatio = &appbsky.EmbedDefs_AspectRatio{Width: o.width, Height: o.height} + } + if o.recordMedia { + pv.Embed = &appbsky.FeedDefs_PostView_Embed{ + EmbedRecordWithMedia_View: &appbsky.EmbedRecordWithMedia_View{ + Record: &appbsky.EmbedRecord_View{ + Record: &appbsky.EmbedRecord_View_Record{ + EmbedRecord_ViewRecord: &appbsky.EmbedRecord_ViewRecord{ + Uri: "at://did:plc:quoted/app.bsky.feed.post/q", + Cid: "bafy-quoted", + Author: &appbsky.ActorDefs_ProfileViewBasic{ + Did: "did:plc:quoted", + Handle: "quoted.bsky.social", + }, + IndexedAt: "2024-01-01T00:00:00Z", + }, + }, + }, + Media: &appbsky.EmbedRecordWithMedia_View_Media{ + EmbedVideo_View: v, + }, + }, + } + } else { + pv.Embed = &appbsky.FeedDefs_PostView_Embed{ + EmbedVideo_View: v, + } + } + } +} + +func TestBuildPostJSONLD_WithVideoObject(t *testing.T) { + thumb := "https://cdn.bsky.app/img/video_thumbnail/plain/did:plc:alice/v@jpeg" + playlist := "https://video.bsky.app/v/did:plc:alice/v/playlist.m3u8" + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "watch this", + withVideoFull(videoEmbedOpts{ + thumbnail: thumb, playlist: playlist, alt: "A trip to the park", + hasAspect: true, width: 16, height: 9, + })) + canonical := "https://bsky.app/profile/alice.bsky.social/post/abc123" + out, err := buildPostJSONLD(pv, nil, canonical, hideEmbedLabels, hideReplyLabels) + if err != nil { + t.Fatal(err) + } + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + video, ok := main["video"].(map[string]any) + if !ok { + t.Fatalf("video missing from mainEntity") + } + if video["@type"] != "VideoObject" { + t.Errorf("@type = %v, want VideoObject", video["@type"]) + } + if video["name"] != "A trip to the park" { + t.Errorf("name = %v, want alt text", video["name"]) + } + if video["description"] != "watch this" { + t.Errorf("description = %v, want post text", video["description"]) + } + if video["thumbnailUrl"] != thumb { + t.Errorf("thumbnailUrl = %v, want %v", video["thumbnailUrl"], thumb) + } + if video["uploadDate"] != pv.IndexedAt { + t.Errorf("uploadDate = %v, want %v", video["uploadDate"], pv.IndexedAt) + } + if video["contentUrl"] != playlist { + t.Errorf("contentUrl = %v, want %v", video["contentUrl"], playlist) + } + if video["embedUrl"] != canonical { + t.Errorf("embedUrl = %v, want %v", video["embedUrl"], canonical) + } + if w, _ := video["width"].(float64); int64(w) != 16 { + t.Errorf("width = %v, want 16", video["width"]) + } + if h, _ := video["height"].(float64); int64(h) != 9 { + t.Errorf("height = %v, want 9", video["height"]) + } + // post thumbnailUrl should equal the video thumb (byte-equal og:image). + if main["thumbnailUrl"] != thumb { + t.Errorf("post thumbnailUrl = %v, want %v", main["thumbnailUrl"], thumb) + } +} + +func TestBuildPostJSONLD_VideoNameFallback(t *testing.T) { + // No alt text -> "Video by @". + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "no alt here", + withVideoFull(videoEmbedOpts{ + playlist: "https://video.bsky.app/p.m3u8", + })) + out, _ := buildPostJSONLD(pv, nil, "u", hideEmbedLabels, hideReplyLabels) + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + video, ok := main["video"].(map[string]any) + if !ok { + t.Fatalf("video missing") + } + if video["name"] != "Video by @alice.bsky.social" { + t.Errorf("name fallback = %v", video["name"]) + } +} + +func TestBuildPostJSONLD_VideoNameFallbackHandleInvalid(t *testing.T) { + // handle.invalid + no alt -> generic fallback. + pv := makePostView("handle.invalid", "did:plc:alice", "abc123", "x", + withVideoFull(videoEmbedOpts{ + playlist: "https://video.bsky.app/p.m3u8", + })) + out, _ := buildPostJSONLD(pv, nil, "u", hideEmbedLabels, hideReplyLabels) + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + video := main["video"].(map[string]any) + if video["name"] != "Video on Bluesky" { + t.Errorf("name fallback = %v, want generic", video["name"]) + } +} + +func TestBuildPostJSONLD_VideoDescriptionFallback(t *testing.T) { + // Empty post text -> description falls back to name so Google's video + // rich-result requirement (description present) is satisfied. + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "", + withVideoFull(videoEmbedOpts{ + playlist: "https://video.bsky.app/p.m3u8", alt: "scenic clip", + })) + out, _ := buildPostJSONLD(pv, nil, "u", hideEmbedLabels, hideReplyLabels) + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + video := main["video"].(map[string]any) + if video["description"] != "scenic clip" { + t.Errorf("description fallback = %v, want name", video["description"]) + } +} + +func TestBuildPostJSONLD_VideoNoAspectRatio(t *testing.T) { + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "x", + withVideoFull(videoEmbedOpts{ + playlist: "https://video.bsky.app/p.m3u8", alt: "alt", + })) + out, _ := buildPostJSONLD(pv, nil, "u", hideEmbedLabels, hideReplyLabels) + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + video := main["video"].(map[string]any) + if _, present := video["width"]; present { + t.Errorf("width should be omitted when AspectRatio missing") + } + if _, present := video["height"]; present { + t.Errorf("height should be omitted when AspectRatio missing") + } +} + +func TestBuildPostJSONLD_VideoMissingPlaylist(t *testing.T) { + // No playlist -> VideoObject suppressed; image[]/thumbnailUrl still set + // from the video thumb (existing behavior). + thumb := "https://cdn.bsky.app/img/video_thumbnail/plain/did:plc:alice/v@jpeg" + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "x", + withVideo(thumb)) + out, _ := buildPostJSONLD(pv, nil, "u", hideEmbedLabels, hideReplyLabels) + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + if _, present := main["video"]; present { + t.Errorf("video without playlist should not produce VideoObject") + } + if main["thumbnailUrl"] != thumb { + t.Errorf("thumbnailUrl should still be set from video thumb, got %v", main["thumbnailUrl"]) + } +} + +func TestBuildPostJSONLD_VideoHiddenEmbed(t *testing.T) { + // Self-labeled hide drops the video, parallel to the image-hide test. + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "nsfw", + withVideoFull(videoEmbedOpts{ + thumbnail: "https://cdn.bsky.app/img/x@jpeg", + playlist: "https://video.bsky.app/p.m3u8", + alt: "should be dropped", + }), + withSelfLabel("porn")) + out, _ := buildPostJSONLD(pv, nil, "u", hideEmbedLabels, hideReplyLabels) + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + if _, present := main["video"]; present { + t.Errorf("hidden-embed post should not emit video") + } +} + +func TestBuildPostJSONLD_VideoInRecordWithMedia(t *testing.T) { + thumb := "https://cdn.bsky.app/img/video_thumbnail/plain/did:plc:alice/v@jpeg" + playlist := "https://video.bsky.app/p.m3u8" + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "quote+video", + withVideoFull(videoEmbedOpts{ + thumbnail: thumb, playlist: playlist, alt: "alt", recordMedia: true, + hasAspect: true, width: 4, height: 3, + })) + out, _ := buildPostJSONLD(pv, nil, "u", hideEmbedLabels, hideReplyLabels) + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + video, ok := main["video"].(map[string]any) + if !ok { + t.Fatalf("video missing on record-with-media post") + } + if video["contentUrl"] != playlist { + t.Errorf("contentUrl = %v, want %v", video["contentUrl"], playlist) + } + if video["thumbnailUrl"] != thumb { + t.Errorf("thumbnailUrl = %v, want %v", video["thumbnailUrl"], thumb) + } + // quote-post still surfaces via isBasedOn alongside the video. + if main["isBasedOn"] != "https://bsky.app/profile/quoted.bsky.social/post/q" { + t.Errorf("isBasedOn = %v", main["isBasedOn"]) + } +} + +func TestBuildPostJSONLD_VideoOnReply(t *testing.T) { + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "main") + *pv.ReplyCount = 1 + thumb := "https://cdn.bsky.app/img/video_thumbnail/plain/did:plc:bob/v@jpeg" + playlist := "https://video.bsky.app/bob.m3u8" + reply := makePostView("bob.bsky.social", "did:plc:bob", "rep1", "watch", + withVideoFull(videoEmbedOpts{ + thumbnail: thumb, playlist: playlist, alt: "bob's clip", + })) + out, _ := buildPostJSONLD(pv, buildReplies(reply), "u", hideEmbedLabels, hideReplyLabels) + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + c := main["comment"].([]any)[0].(map[string]any) + video, ok := c["video"].(map[string]any) + if !ok { + t.Fatalf("reply video missing") + } + if video["@type"] != "VideoObject" { + t.Errorf("reply video @type = %v", video["@type"]) + } + if video["name"] != "bob's clip" { + t.Errorf("reply video name = %v", video["name"]) + } + if video["contentUrl"] != playlist { + t.Errorf("reply video contentUrl = %v", video["contentUrl"]) + } + if video["embedUrl"] != "https://bsky.app/profile/bob.bsky.social/post/rep1" { + t.Errorf("reply video embedUrl = %v", video["embedUrl"]) + } +} + +func TestBuildPostJSONLD_NoVideoNoField(t *testing.T) { + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "no embed") + out, _ := buildPostJSONLD(pv, nil, "u", hideEmbedLabels, hideReplyLabels) + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + if _, present := main["video"]; present { + t.Errorf("post without video should not include video field") + } +} + +func TestBuildProfileJSONLD_HasPartVideo(t *testing.T) { + pv := newProfileViewDetailed() + playlist := "https://video.bsky.app/p.m3u8" + post := makePostView("alice.bsky.social", "did:plc:alice", "rp1", "see", + withVideoFull(videoEmbedOpts{ + playlist: playlist, alt: "alt", + })) + out, _ := buildProfileJSONLD(pv, []*appbsky.FeedDefs_PostView{post}, hideEmbedLabels, hideReplyLabels) + page := unmarshalLD(t, out) + hp := page["hasPart"].([]any) + if len(hp) != 1 { + t.Fatalf("expected 1 hasPart entry, got %d", len(hp)) + } + video, ok := hp[0].(map[string]any)["video"].(map[string]any) + if !ok { + t.Fatalf("hasPart entry should carry video, got %v", hp[0]) + } + if video["contentUrl"] != playlist { + t.Errorf("hasPart video contentUrl = %v", video["contentUrl"]) + } + if video["embedUrl"] != "https://bsky.app/profile/alice.bsky.social/post/rp1" { + t.Errorf("hasPart video embedUrl = %v", video["embedUrl"]) + } +} + +// handle.invalid authors must still produce a non-empty embedUrl on the +// VideoObject. The handle-form URL is unusable, so we fall back to the +// DID-form URL derived from the AT-URI authority. Without this fallback, +// omitempty drops embedUrl and Google's video indexer loses the canonical +// page reference. +func TestBuildPostJSONLD_VideoHandleInvalidEmbedURL(t *testing.T) { + playlist := "https://video.bsky.app/p.m3u8" + pv := makePostView("handle.invalid", "did:plc:alice", "abc123", "watch", + withVideoFull(videoEmbedOpts{ + playlist: playlist, alt: "scenic clip", + })) + canonical := "https://bsky.app/profile/did:plc:alice/post/abc123" + out, _ := buildPostJSONLD(pv, nil, canonical, hideEmbedLabels, hideReplyLabels) + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + video, ok := main["video"].(map[string]any) + if !ok { + t.Fatalf("video missing on handle.invalid post") + } + if video["embedUrl"] != canonical { + t.Errorf("embedUrl = %v, want %v", video["embedUrl"], canonical) + } + if video["contentUrl"] != playlist { + t.Errorf("contentUrl = %v, want %v", video["contentUrl"], playlist) + } +} + +// Same fallback applies to videos on replies whose author is handle.invalid. +func TestBuildPostJSONLD_VideoHandleInvalidEmbedURL_Reply(t *testing.T) { + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "main") + *pv.ReplyCount = 1 + playlist := "https://video.bsky.app/bob.m3u8" + reply := makePostView("handle.invalid", "did:plc:bob", "rep1", "watch", + withVideoFull(videoEmbedOpts{ + playlist: playlist, alt: "bob's clip", + })) + out, _ := buildPostJSONLD(pv, buildReplies(reply), "u", hideEmbedLabels, hideReplyLabels) + main := unmarshalLD(t, out)["mainEntity"].(map[string]any) + c := main["comment"].([]any)[0].(map[string]any) + video, ok := c["video"].(map[string]any) + if !ok { + t.Fatalf("reply video missing") + } + want := "https://bsky.app/profile/did:plc:bob/post/rep1" + if video["embedUrl"] != want { + t.Errorf("reply video embedUrl = %v, want %v", video["embedUrl"], want) + } +} diff --git a/bskyweb/cmd/bskyweb/labels.go b/bskyweb/cmd/bskyweb/labels.go new file mode 100644 index 0000000000..4055d4b9c8 --- /dev/null +++ b/bskyweb/cmd/bskyweb/labels.go @@ -0,0 +1,22 @@ +package main + +import ( + appbsky "github.com/bluesky-social/indigo/api/bsky" +) + +// Helpers for inspecting profile / post labels. + +// profileRequiresAuth reports whether the profile has self-applied the +// !no-unauthenticated label. SSR responses for these profiles omit post +// content beyond minimal identity. +func profileRequiresAuth(pv *appbsky.ActorDefs_ProfileViewDetailed) bool { + if pv == nil { + return false + } + for _, label := range pv.Labels { + if label.Src == pv.Did && label.Val == "!no-unauthenticated" { + return true + } + } + return false +} diff --git a/bskyweb/cmd/bskyweb/labels_test.go b/bskyweb/cmd/bskyweb/labels_test.go new file mode 100644 index 0000000000..d55e31b207 --- /dev/null +++ b/bskyweb/cmd/bskyweb/labels_test.go @@ -0,0 +1,79 @@ +package main + +import ( + "testing" + + comatprototypes "github.com/bluesky-social/indigo/api/atproto" + appbsky "github.com/bluesky-social/indigo/api/bsky" +) + +func TestProfileRequiresAuth(t *testing.T) { + negTrue := true + + tests := []struct { + name string + pv *appbsky.ActorDefs_ProfileViewDetailed + want bool + }{ + { + name: "nil profile", + pv: nil, + want: false, + }, + { + name: "no labels", + pv: &appbsky.ActorDefs_ProfileViewDetailed{Did: "did:plc:alice"}, + want: false, + }, + { + name: "self-applied !no-unauthenticated", + pv: &appbsky.ActorDefs_ProfileViewDetailed{ + Did: "did:plc:alice", + Labels: []*comatprototypes.LabelDefs_Label{ + {Src: "did:plc:alice", Val: "!no-unauthenticated"}, + }, + }, + want: true, + }, + { + name: "label from a different src does not gate", + pv: &appbsky.ActorDefs_ProfileViewDetailed{ + Did: "did:plc:alice", + Labels: []*comatprototypes.LabelDefs_Label{ + {Src: "did:plc:labeler", Val: "!no-unauthenticated"}, + }, + }, + want: false, + }, + { + name: "different label value does not gate", + pv: &appbsky.ActorDefs_ProfileViewDetailed{ + Did: "did:plc:alice", + Labels: []*comatprototypes.LabelDefs_Label{ + {Src: "did:plc:alice", Val: "spam"}, + }, + }, + want: false, + }, + { + // Negation isn't honored — matches prior inline behavior in + // WebPost / WebProfile / WebProfileRSS. + name: "negated label still triggers (matches prior behavior)", + pv: &appbsky.ActorDefs_ProfileViewDetailed{ + Did: "did:plc:alice", + Labels: []*comatprototypes.LabelDefs_Label{ + {Src: "did:plc:alice", Val: "!no-unauthenticated", Neg: &negTrue}, + }, + }, + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := profileRequiresAuth(tt.pv); got != tt.want { + t.Errorf("got %v, want %v", got, tt.want) + } + }) + } +} diff --git a/bskyweb/cmd/bskyweb/main.go b/bskyweb/cmd/bskyweb/main.go index da8a30953d..dba852f79b 100644 --- a/bskyweb/cmd/bskyweb/main.go +++ b/bskyweb/cmd/bskyweb/main.go @@ -46,6 +46,12 @@ func run(args []string) { Required: false, EnvVars: []string{"OGCARD_HOST"}, }, + &cli.StringFlag{ + Name: "chat-host", + Usage: "scheme, hostname, and port of chat appview", + Required: false, + EnvVars: []string{"CHAT_HOST"}, + }, &cli.StringFlag{ Name: "http-address", Usage: "Specify the local IP/port to bind to", @@ -53,6 +59,13 @@ func run(args []string) { Value: ":8100", EnvVars: []string{"HTTP_ADDRESS"}, }, + &cli.StringFlag{ + Name: "metrics-address", + Usage: "Specify the local IP/port to bind the metrics server to", + Required: false, + Value: ":9090", + EnvVars: []string{"METRICS_HTTP_ADDRESS"}, + }, &cli.StringFlag{ Name: "link-host", Usage: "scheme, hostname, and port of link service", diff --git a/bskyweb/cmd/bskyweb/render_test.go b/bskyweb/cmd/bskyweb/render_test.go new file mode 100644 index 0000000000..902476709f --- /dev/null +++ b/bskyweb/cmd/bskyweb/render_test.go @@ -0,0 +1,299 @@ +package main + +import ( + "bytes" + "encoding/json" + "regexp" + "strings" + "testing" + + "github.com/bluesky-social/social-app/bskyweb" + "github.com/flosch/pongo2/v6" +) + +// renderTemplate executes a template using the live renderer and returns +// the output. base.html includes templates/scripts.html, which is generated +// by `yarn build-web`; skip the test if it's missing. +func renderTemplate(t *testing.T, name string, ctx pongo2.Context) string { + t.Helper() + if _, err := bskyweb.TemplateFS.ReadFile("templates/scripts.html"); err != nil { + t.Skip("templates/scripts.html not present (run yarn build-web first)") + } + r := NewRenderer("templates/", &bskyweb.TemplateFS, false) + tmpl, err := r.TemplateSet.FromCache(name) + if err != nil { + t.Fatalf("template load %q: %v", name, err) + } + var buf bytes.Buffer + if err := tmpl.ExecuteWriter(ctx, &buf); err != nil { + t.Fatalf("template execute %q: %v", name, err) + } + return buf.String() +} + +var jsonLDRe = regexp.MustCompile(`(?s)`) + +// extractJSONLD pulls out the body of the application/ld+json script tag. +func extractJSONLD(t *testing.T, html string) string { + t.Helper() + m := jsonLDRe.FindStringSubmatch(html) + if m == nil { + t.Fatalf("no application/ld+json script tag in rendered HTML:\n%s", html) + } + return strings.TrimSpace(m[1]) +} + +func TestRenderBase_NoindexMeta(t *testing.T) { + html := renderTemplate(t, "base.html", pongo2.Context{"noindex": true, "nofollow": true}) + if !strings.Contains(html, ``) { + t.Errorf("expected combined noindex,nofollow meta; got:\n%s", html) + } +} + +func TestRenderPost_EmitsJSONLD(t *testing.T) { + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "hello") + ld, err := buildPostJSONLD(pv, nil, "https://bsky.app/profile/alice.bsky.social/post/abc123", hideEmbedLabels, hideReplyLabels) + if err != nil { + t.Fatal(err) + } + html := renderTemplate(t, "post.html", pongo2.Context{ + "postView": pv, + "requestURI": "https://bsky.app/profile/alice.bsky.social/post/abc123", + "canonicalURL": "https://bsky.app/profile/alice.bsky.social/post/abc123", + "postJSONLD": ld, + }) + + body := extractJSONLD(t, html) + var parsed map[string]any + if err := json.Unmarshal([]byte(body), &parsed); err != nil { + t.Fatalf("rendered JSON-LD does not parse: %v\n%s", err, body) + } + if parsed["@type"] != "WebPage" { + t.Errorf("expected WebPage envelope, got %v", parsed["@type"]) + } + // Canonical link should be the handle-form URL. + if !strings.Contains(html, ``) { + t.Errorf("canonical link missing or wrong:\n%s", html) + } +} + +func TestRenderPost_OGImageMatchesJSONLD(t *testing.T) { + thumb1 := "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:alice/abc@jpeg" + thumb2 := "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:alice/def@jpeg" + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "look", withImages(thumb1, thumb2)) + ld, _ := buildPostJSONLD(pv, nil, "https://bsky.app/profile/alice.bsky.social/post/abc123", hideEmbedLabels, hideReplyLabels) + html := renderTemplate(t, "post.html", pongo2.Context{ + "postView": pv, + "requestURI": "https://bsky.app/profile/alice.bsky.social/post/abc123", + "canonicalURL": "https://bsky.app/profile/alice.bsky.social/post/abc123", + "postJSONLD": ld, + "imgThumbUrls": []string{thumb1, thumb2}, + }) + + // og:image and JSON-LD image[] must be byte-identical. + if !strings.Contains(html, ``) { + t.Errorf("og:image[0] not found in rendered HTML") + } + body := extractJSONLD(t, html) + var parsed map[string]any + _ = json.Unmarshal([]byte(body), &parsed) + main := parsed["mainEntity"].(map[string]any) + imgs := main["image"].([]any) + if imgs[0] != thumb1 || main["thumbnailUrl"] != thumb1 { + t.Errorf("JSON-LD image strings drifted from og:image; image[0]=%v thumbnailUrl=%v", + imgs[0], main["thumbnailUrl"]) + } +} + +// Gallery posts must hit the same og:image / JSON-LD image[] byte-equality +// contract that legacy images posts do. Regression guard for the +// app.bsky.embed.gallery extraction path. +func TestRenderPost_OGImageMatchesJSONLD_Gallery(t *testing.T) { + thumb1 := "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:alice/g1@jpeg" + thumb2 := "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:alice/g2@jpeg" + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "gallery", withGallery(thumb1, thumb2)) + thumbs := extractPostMedia(pv, false) + ld, _ := buildPostJSONLD(pv, nil, "https://bsky.app/profile/alice.bsky.social/post/abc123", hideEmbedLabels, hideReplyLabels) + html := renderTemplate(t, "post.html", pongo2.Context{ + "postView": pv, + "requestURI": "https://bsky.app/profile/alice.bsky.social/post/abc123", + "canonicalURL": "https://bsky.app/profile/alice.bsky.social/post/abc123", + "postJSONLD": ld, + "imgThumbUrls": thumbs, + }) + + if !strings.Contains(html, ``) { + t.Errorf("og:image[0] not found in rendered HTML for gallery post") + } + if !strings.Contains(html, ``) { + t.Errorf("og:image[1] not found in rendered HTML for gallery post") + } + body := extractJSONLD(t, html) + var parsed map[string]any + _ = json.Unmarshal([]byte(body), &parsed) + main := parsed["mainEntity"].(map[string]any) + imgs := main["image"].([]any) + if len(imgs) != 2 || imgs[0] != thumb1 || main["thumbnailUrl"] != thumb1 { + t.Errorf("JSON-LD image strings drifted from og:image for gallery; image=%v thumbnailUrl=%v", + imgs, main["thumbnailUrl"]) + } +} + +func TestRenderPost_FallsBackToCanonicalizeFilter(t *testing.T) { + // Without canonicalURL, the template falls back to requestURI|canonicalize_url. + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "hi") + ld, _ := buildPostJSONLD(pv, nil, "u", hideEmbedLabels, hideReplyLabels) + html := renderTemplate(t, "post.html", pongo2.Context{ + "postView": pv, + "requestURI": "https://bsky.app/profile/alice.bsky.social/post/abc123?utm=foo", + "postJSONLD": ld, + }) + if !strings.Contains(html, ``) { + t.Errorf("expected canonicalize_url filter to strip query; got:\n%s", html) + } +} + +func TestRenderProfile_EmitsJSONLD(t *testing.T) { + pv := newProfileViewDetailed() + ld, err := buildProfileJSONLD(pv, nil, hideEmbedLabels, hideReplyLabels) + if err != nil { + t.Fatal(err) + } + html := renderTemplate(t, "profile.html", pongo2.Context{ + "profileView": pv, + "requestURI": "https://bsky.app/profile/alice.bsky.social", + "canonicalURL": "https://bsky.app/profile/alice.bsky.social", + "profileJSONLD": ld, + }) + + body := extractJSONLD(t, html) + var parsed map[string]any + if err := json.Unmarshal([]byte(body), &parsed); err != nil { + t.Fatalf("rendered JSON-LD does not parse: %v\n%s", err, body) + } + if parsed["@type"] != "ProfilePage" { + t.Errorf("expected ProfilePage, got %v", parsed["@type"]) + } +} + +// Regression: auth-required profiles must still emit ProfilePage JSON-LD +// (without hasPart). Previously regressed when WebProfile short-circuited +// before buildProfileJSONLD. +func TestRenderProfile_AuthRequiredEmitsJSONLD(t *testing.T) { + pv := newProfileViewDetailed() + ld, err := buildProfileJSONLD(pv, nil, hideEmbedLabels, hideReplyLabels) + if err != nil { + t.Fatal(err) + } + html := renderTemplate(t, "profile.html", pongo2.Context{ + "profileView": pv, + "requestURI": "https://bsky.app/profile/alice.bsky.social", + "requiresAuth": true, + "profileJSONLD": ld, + }) + + body := extractJSONLD(t, html) + var parsed map[string]any + if err := json.Unmarshal([]byte(body), &parsed); err != nil { + t.Fatalf("rendered JSON-LD does not parse: %v\n%s", err, body) + } + if parsed["@type"] != "ProfilePage" { + t.Errorf("expected ProfilePage, got %v", parsed["@type"]) + } + if _, present := parsed["hasPart"]; present { + t.Errorf("auth-required profile should not have hasPart") + } +} + +// og:url and must emit the same URL. +func TestRenderPost_OGUrlMatchesCanonical(t *testing.T) { + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "hi") + ld, _ := buildPostJSONLD(pv, nil, "u", hideEmbedLabels, hideReplyLabels) + canonical := "https://bsky.app/profile/alice.bsky.social/post/abc123" + html := renderTemplate(t, "post.html", pongo2.Context{ + "postView": pv, + "requestURI": "https://bsky.app/profile/did:plc:alice/post/abc123", + "canonicalURL": canonical, + "postJSONLD": ld, + }) + if !strings.Contains(html, ``) { + t.Errorf("og:url should equal canonical URL when set; got:\n%s", html) + } + if !strings.Contains(html, ``) { + t.Errorf("canonical link missing or wrong:\n%s", html) + } + // DID-form request URI must not leak into og:url. + if strings.Contains(html, ``) { + t.Errorf("og:url should not echo DID-form request URI when canonical is set") + } +} + +// og:video must emit even when there is no thumbnail. Previously the +// {% if videoUrl %} block was nested inside {% if imgThumbUrls %}, so a +// video without a thumbnail dropped og:video entirely. +func TestRenderPost_VideoWithoutThumbnailEmitsOGVideo(t *testing.T) { + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "watch") + ld, _ := buildPostJSONLD(pv, nil, "u", hideEmbedLabels, hideReplyLabels) + videoURL := "https://video.bsky.app/v.m3u8" + html := renderTemplate(t, "post.html", pongo2.Context{ + "postView": pv, + "requestURI": "https://bsky.app/profile/alice.bsky.social/post/abc123", + "canonicalURL": "https://bsky.app/profile/alice.bsky.social/post/abc123", + "postJSONLD": ld, + "videoUrl": videoURL, + "videoType": "application/x-mpegURL", + }) + if !strings.Contains(html, ``) { + t.Errorf("og:video should emit even without imgThumbUrls; got:\n%s", html) + } + if !strings.Contains(html, ``) { + t.Errorf("og:video:type should emit even without imgThumbUrls; got:\n%s", html) + } +} + +// Auth-required posts must emit noindex,nofollow so the stub page (no body +// text, no comments) is not indexed. +func TestRenderPost_AuthRequiredNoindex(t *testing.T) { + html := renderTemplate(t, "post.html", pongo2.Context{ + "requiresAuth": true, + "profileHandle": "alice.bsky.social", + "requestURI": "https://bsky.app/profile/alice.bsky.social/post/abc123", + "canonicalURL": "https://bsky.app/profile/alice.bsky.social/post/abc123", + "noindex": true, + "nofollow": true, + }) + if !strings.Contains(html, ``) { + t.Errorf("auth-required post should emit noindex,nofollow; got:\n%s", html) + } +} + +// Auth-required profiles must emit noindex,nofollow. +func TestRenderProfile_AuthRequiredNoindex(t *testing.T) { + pv := newProfileViewDetailed() + html := renderTemplate(t, "profile.html", pongo2.Context{ + "profileView": pv, + "requestURI": "https://bsky.app/profile/alice.bsky.social", + "requiresAuth": true, + "noindex": true, + "nofollow": true, + }) + if !strings.Contains(html, ``) { + t.Errorf("auth-required profile should emit noindex,nofollow; got:\n%s", html) + } +} + +// Public posts must NOT emit a robots meta tag. Guards against an accidental +// flip of the noindex flag for indexable pages. +func TestRenderPost_PublicNoNoindex(t *testing.T) { + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "hello") + ld, _ := buildPostJSONLD(pv, nil, "https://bsky.app/profile/alice.bsky.social/post/abc123", hideEmbedLabels, hideReplyLabels) + html := renderTemplate(t, "post.html", pongo2.Context{ + "postView": pv, + "requestURI": "https://bsky.app/profile/alice.bsky.social/post/abc123", + "canonicalURL": "https://bsky.app/profile/alice.bsky.social/post/abc123", + "postJSONLD": ld, + }) + if strings.Contains(html, ` for OG previews. + var chatXrpcc *xrpc.Client + if chatHost != "" { + chatXrpcc = &xrpc.Client{ + Client: cliutil.NewHttpClient(), + Host: chatHost, + } + } + // httpd var ( httpTimeout = 2 * time.Minute @@ -105,12 +124,14 @@ func serve(cctx *cli.Context) error { // server // server := &Server{ - echo: e, - xrpcc: xrpcc, + echo: e, + xrpcc: xrpcc, + chatXrpcc: chatXrpcc, cfg: &Config{ debug: debug, httpAddress: httpAddress, appviewHost: appviewHost, + chatHost: chatHost, ogcardHost: ogcardHost, linkHost: linkHost, ipccHost: ipccHost, @@ -201,6 +222,14 @@ func serve(cctx *cli.Context) error { RedirectCode: http.StatusFound, })) + echoprom := echoprometheus.NewMiddlewareWithConfig( + echoprometheus.MiddlewareConfig{ + DoNotUseRequestPathFor404: true, + }, + ) + + e.Use(echoprom) + // CORS middleware e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ AllowOrigins: corsOrigins, @@ -271,57 +300,50 @@ func serve(cctx *cli.Context) error { // generic routes e.GET("/hashtag/:tag", server.WebGeneric) e.GET("/topic/:topic", server.WebGeneric) - e.GET("/search", server.WebGeneric) - e.GET("/feeds", server.WebGeneric) - e.GET("/notifications", server.WebGeneric) - e.GET("/notifications/settings", server.WebGeneric) - e.GET("/notifications/activity", server.WebGeneric) - e.GET("/lists", server.WebGeneric) - e.GET("/moderation", server.WebGeneric) - e.GET("/moderation/modlists", server.WebGeneric) - e.GET("/moderation/muted-accounts", server.WebGeneric) - e.GET("/moderation/blocked-accounts", server.WebGeneric) - e.GET("/moderation/verification-settings", server.WebGeneric) - e.GET("/settings", server.WebGeneric) - e.GET("/settings/language", server.WebGeneric) - e.GET("/settings/app-passwords", server.WebGeneric) - e.GET("/settings/following-feed", server.WebGeneric) - e.GET("/settings/saved-feeds", server.WebGeneric) - e.GET("/settings/threads", server.WebGeneric) - e.GET("/settings/external-embeds", server.WebGeneric) - e.GET("/settings/accessibility", server.WebGeneric) - e.GET("/settings/appearance", server.WebGeneric) - e.GET("/settings/account", server.WebGeneric) - e.GET("/settings/automation-label", server.WebGeneric) - e.GET("/settings/privacy-and-security", server.WebGeneric) - e.GET("/settings/privacy-and-security/activity", server.WebGeneric) - e.GET("/settings/content-and-media", server.WebGeneric) - e.GET("/settings/interests", server.WebGeneric) - e.GET("/settings/about", server.WebGeneric) - e.GET("/settings/notifications", server.WebGeneric) - e.GET("/settings/notifications/replies", server.WebGeneric) - e.GET("/settings/notifications/mentions", server.WebGeneric) - e.GET("/settings/notifications/quotes", server.WebGeneric) - e.GET("/settings/notifications/likes", server.WebGeneric) - e.GET("/settings/notifications/reposts", server.WebGeneric) - e.GET("/settings/notifications/new-followers", server.WebGeneric) - e.GET("/settings/notifications/likes-on-reposts", server.WebGeneric) - e.GET("/settings/notifications/reposts-on-reposts", server.WebGeneric) - e.GET("/settings/notifications/activity", server.WebGeneric) - e.GET("/settings/notifications/miscellaneous", server.WebGeneric) - e.GET("/sys/debug", server.WebGeneric) - e.GET("/sys/debug-mod", server.WebGeneric) - e.GET("/sys/log", server.WebGeneric) + e.GET("/search", server.WebGenericNoindex) + e.GET("/feeds", server.WebGenericNoindex) + e.GET("/notifications", server.WebGenericNoindex) + e.GET("/notifications/settings", server.WebGenericNoindex) + e.GET("/notifications/activity", server.WebGenericNoindex) + e.GET("/lists", server.WebGenericNoindex) + e.GET("/moderation", server.WebGenericNoindex) + e.GET("/moderation/modlists", server.WebGenericNoindex) + e.GET("/moderation/muted-accounts", server.WebGenericNoindex) + e.GET("/moderation/blocked-accounts", server.WebGenericNoindex) + e.GET("/moderation/verification-settings", server.WebGenericNoindex) + e.GET("/settings", server.WebGenericNoindex) + e.GET("/settings/language", server.WebGenericNoindex) + e.GET("/settings/app-passwords", server.WebGenericNoindex) + e.GET("/settings/following-feed", server.WebGenericNoindex) + e.GET("/settings/saved-feeds", server.WebGenericNoindex) + e.GET("/settings/threads", server.WebGenericNoindex) + e.GET("/settings/external-embeds", server.WebGenericNoindex) + e.GET("/settings/accessibility", server.WebGenericNoindex) + e.GET("/settings/appearance", server.WebGenericNoindex) + e.GET("/settings/account", server.WebGenericNoindex) + e.GET("/settings/automation-label", server.WebGenericNoindex) + e.GET("/settings/privacy-and-security", server.WebGenericNoindex) + e.GET("/settings/privacy-and-security/activity", server.WebGenericNoindex) + e.GET("/settings/content-and-media", server.WebGenericNoindex) + e.GET("/settings/interests", server.WebGenericNoindex) + e.GET("/settings/about", server.WebGenericNoindex) + e.GET("/settings/notifications", server.WebGenericNoindex) + e.GET("/sys/debug", server.WebGenericNoindex) + e.GET("/sys/debug-mod", server.WebGenericNoindex) + e.GET("/sys/log", server.WebGenericNoindex) e.GET("/support", server.WebGeneric) e.GET("/support/privacy", server.WebGeneric) e.GET("/support/tos", server.WebGeneric) e.GET("/support/community-guidelines", server.WebGeneric) e.GET("/support/copyright", server.WebGeneric) - e.GET("/intent/compose", server.WebGeneric) - e.GET("/intent/verify-email", server.WebGeneric) - e.GET("/intent/age-assurance", server.WebGeneric) - e.GET("/messages", server.WebGeneric) - e.GET("/messages/:conversation", server.WebGeneric) + e.GET("/intent/compose", server.WebGenericNoindexNofollow) + e.GET("/intent/verify-email", server.WebGenericNoindexNofollow) + e.GET("/intent/age-assurance", server.WebGenericNoindexNofollow) + e.GET("/messages", server.WebGenericNoindex) + e.GET("/messages/inbox", server.WebGenericNoindex) + e.GET("/messages/:conversation", server.WebGenericNoindex) + e.GET("/messages/:conversation/settings", server.WebGenericNoindex) + e.GET("/messages/:conversation/requests", server.WebGenericNoindex) // profile endpoints; only first populates info e.GET("/profile/:handleOrDID", server.WebProfile) @@ -345,11 +367,14 @@ func serve(cctx *cli.Context) error { // starter packs e.GET("/starter-pack/:handleOrDID/:rkey", server.WebStarterPack) - e.GET("/starter-pack-short/:code", server.WebGeneric) + e.GET("/starter-pack-short/:code", server.WebGenericNoindex) e.GET("/start/:handleOrDID/:rkey", server.WebStarterPack) + // chat invites + e.GET("/chat/:code", server.WebChatInvite) + // bookmarks - e.GET("/saved", server.WebGeneric) + e.GET("/saved", server.WebGenericNoindex) // ipcc e.GET("/ipcc", server.WebIpCC) @@ -366,6 +391,19 @@ func serve(cctx *cli.Context) error { e.Group("/:linkId", server.LinkProxyMiddleware(linkUrl)) } + metricsHttpd, metricsListener, err := newMetricsHTTPServer(metricsAddress) + if err != nil { + return err + } + server.metricsHttpd = metricsHttpd + + log.Infof("starting metrics server address=%s", metricsAddress) + go func() { + if err := metricsHttpd.Serve(metricsListener); err != nil && !errors.Is(err, http.ErrServerClosed) { + log.Errorf("metrics HTTP server shutting down unexpectedly: %s", err) + } + }() + // Start the server. log.Infof("starting server address=%s", httpAddress) go func() { @@ -398,6 +436,24 @@ func serve(cctx *cli.Context) error { return nil } +func newMetricsHTTPServer(address string) (*http.Server, net.Listener, error) { + metricsMux := http.NewServeMux() + metricsMux.Handle("/metrics", promhttp.Handler()) + + metricsHttpd := &http.Server{ + Addr: address, + Handler: metricsMux, + ReadHeaderTimeout: 5 * time.Second, + } + + metricsListener, err := net.Listen("tcp", address) + if err != nil { + return nil, nil, fmt.Errorf("listen metrics address %s: %w", address, err) + } + + return metricsHttpd, metricsListener, nil +} + func (srv *Server) ServeHTTP(rw http.ResponseWriter, req *http.Request) { srv.echo.ServeHTTP(rw, req) } @@ -408,7 +464,18 @@ func (srv *Server) Shutdown() error { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - return srv.httpd.Shutdown(ctx) + var shutdownErr error + if srv.metricsHttpd != nil { + if err := srv.metricsHttpd.Shutdown(ctx); err != nil { + shutdownErr = fmt.Errorf("metrics HTTP server shutdown error: %w", err) + } + } + + if err := srv.httpd.Shutdown(ctx); err != nil { + return errors.Join(shutdownErr, err) + } + + return shutdownErr } // NewTemplateContext returns a new pongo2 context with some default values. @@ -416,6 +483,8 @@ func (srv *Server) NewTemplateContext() pongo2.Context { return pongo2.Context{ "staticCDNHost": srv.cfg.staticCDNHost, "favicon": fmt.Sprintf("%s/static/favicon.png", srv.cfg.staticCDNHost), + "noindex": false, + "nofollow": false, } } @@ -468,10 +537,38 @@ func (srv *Server) LinkProxyMiddleware(url *url.URL) echo.MiddlewareFunc { ) } +// renderOptions controls per-request rendering flags for the generic web handler. +type renderOptions struct { + noindex bool + nofollow bool +} + +// webGeneric returns a handler that renders the base SPA shell with the given +// render options applied to the template context. +func (srv *Server) webGeneric(c echo.Context, o renderOptions) error { + data := srv.NewTemplateContext() + data["noindex"] = o.noindex + data["nofollow"] = o.nofollow + return c.Render(http.StatusOK, "base.html", data) +} + // handler for endpoint that have no specific server-side handling func (srv *Server) WebGeneric(c echo.Context) error { - data := srv.NewTemplateContext() - return c.Render(http.StatusOK, "base.html", data) + return srv.webGeneric(c, renderOptions{}) +} + +// handler for routes that should not be indexed by search engines +// (e.g. auth-only user-state surfaces, internal/debug pages, action/intent dispatch URLs, search results) +func (srv *Server) WebGenericNoindex(c echo.Context) error { + return srv.webGeneric(c, renderOptions{noindex: true}) +} + +// handler for action/intent dispatch URLs (e.g. /intent/compose). These accept +// arbitrary query parameters from arbitrary third-party referrers, so we treat +// them as link-graph dead-ends in addition to noindex. Anything legitimately +// reachable from a hydrated intent page is also reachable via its canonical URL. +func (srv *Server) WebGenericNoindexNofollow(c echo.Context) error { + return srv.webGeneric(c, renderOptions{noindex: true, nofollow: true}) } func (srv *Server) WebHome(c echo.Context) error { @@ -491,6 +588,20 @@ var hideEmbedLabels = map[string]bool{ "sensitive": true, } +// Replies surfaced into a post's JSON-LD comment[] are dropped entirely when +// any of these labels are present (in addition to hideEmbedLabels). Targets +// abuse/spam in third-party reply text, since reply text would otherwise be +// emitted into the parent post's structured data. +var hideReplyLabels = map[string]bool{ + "!hide": true, + "!warn": true, + "porn": true, + "sexual": true, + "nudity": true, + "graphic-media": true, + "spam": true, +} + func (srv *Server) WebPost(c echo.Context) error { ctx := c.Request().Context() data := srv.NewTemplateContext() @@ -515,18 +626,25 @@ func (srv *Server) WebPost(c echo.Context) error { log.Warnf("failed to fetch profile for: %s\t%v", identifier, err) return c.Render(http.StatusOK, "post.html", data) } - unauthedViewingOkay := true - for _, label := range pv.Labels { - if label.Src == pv.Did && label.Val == "!no-unauthenticated" { - unauthedViewingOkay = false - } - } + unauthedViewingOkay := !profileRequiresAuth(pv) req := c.Request() + requestURI := fmt.Sprintf("https://%s%s", req.Host, req.URL.Path) + + // Always prefer the handle-form URL so JSON-LD `url` and + // match. Falls back to requestURI when the + // handle is unusable (template strips query/fragment). + canonicalURL := bskyPostURL(pv.Handle, rkey.String()) + if !unauthedViewingOkay { // Provide minimal OpenGraph data for auth-required posts - data["requestURI"] = fmt.Sprintf("https://%s%s", req.Host, req.URL.Path) + data["requestURI"] = requestURI + if canonicalURL != "" { + data["canonicalURL"] = canonicalURL + } data["requiresAuth"] = true + data["noindex"] = true + data["nofollow"] = true data["profileHandle"] = pv.Handle if pv.DisplayName != nil { data["profileDisplayName"] = *pv.DisplayName @@ -542,81 +660,44 @@ func (srv *Server) WebPost(c echo.Context) error { return c.Render(http.StatusOK, "post.html", data) } - postView := tpv.Thread.FeedDefs_ThreadViewPost.Post + threadView := tpv.Thread.FeedDefs_ThreadViewPost + if threadView == nil || threadView.Post == nil { + return c.Render(http.StatusOK, "post.html", data) + } + postView := threadView.Post data["postView"] = postView - data["requestURI"] = fmt.Sprintf("https://%s%s", req.Host, req.URL.Path) + data["requestURI"] = requestURI + if canonicalURL != "" { + data["canonicalURL"] = canonicalURL + } - // If any undesirable labels are set, the embed will not be included in - // metadata - isEmbedHidden := false - for _, label := range postView.Labels { - isNeg := label.Neg != nil && *label.Neg - if hideEmbedLabels[label.Val] && !isNeg { - isEmbedHidden = true - break + // Share extraction helpers with jsonld.go so og:image and JSON-LD + // image[] are byte-identical (per Google's requirement). + isEmbedHidden := postEmbedHidden(postView, hideEmbedLabels) + data["postText"] = postRecordText(postView) + + if thumbs := extractPostMedia(postView, isEmbedHidden); len(thumbs) > 0 { + data["imgThumbUrls"] = thumbs + } + if vm := extractVideoMeta(postView, isEmbedHidden); vm.URL != "" { + data["videoUrl"] = vm.URL + data["videoType"] = vm.Type + if vm.HasSize { + data["videoWidth"] = vm.Width + data["videoHeight"] = vm.Height } } - if postView.Record != nil { - postRecord, ok := postView.Record.Val.(*appbsky.FeedPost) - if ok { - data["postText"] = ExpandPostText(postRecord) - - if !isEmbedHidden && postRecord.Labels != nil && postRecord.Labels.LabelDefs_SelfLabels != nil { - for _, label := range postRecord.Labels.LabelDefs_SelfLabels.Values { - if hideEmbedLabels[label.Val] { - isEmbedHidden = true - break - } - } - } - } + // Build JSON-LD. Fall back to requestURI when handle is unusable so the + // envelope url is never empty. + jsonldURL := canonicalURL + if jsonldURL == "" { + jsonldURL = requestURI } - - if postView.Embed != nil && !isEmbedHidden { - hasImages := postView.Embed.EmbedImages_View != nil - hasVideo := postView.Embed.EmbedVideo_View != nil - hasMedia := postView.Embed.EmbedRecordWithMedia_View != nil && postView.Embed.EmbedRecordWithMedia_View.Media != nil - hasMediaImages := hasMedia && postView.Embed.EmbedRecordWithMedia_View.Media.EmbedImages_View != nil - hasMediaVideo := hasMedia && postView.Embed.EmbedRecordWithMedia_View.Media.EmbedVideo_View != nil - - if hasImages { - var thumbUrls []string - for i := range postView.Embed.EmbedImages_View.Images { - thumbUrls = append(thumbUrls, postView.Embed.EmbedImages_View.Images[i].Thumb) - } - data["imgThumbUrls"] = thumbUrls - } else if hasVideo { - if postView.Embed.EmbedVideo_View.Thumbnail != nil { - data["imgThumbUrls"] = []string{*postView.Embed.EmbedVideo_View.Thumbnail} - } - if postView.Embed.EmbedVideo_View.Playlist != "" { - data["videoUrl"] = postView.Embed.EmbedVideo_View.Playlist - data["videoType"] = "application/vnd.apple.mpegurl" - if postView.Embed.EmbedVideo_View.AspectRatio != nil { - data["videoWidth"] = postView.Embed.EmbedVideo_View.AspectRatio.Width - data["videoHeight"] = postView.Embed.EmbedVideo_View.AspectRatio.Height - } - } - } else if hasMediaImages { - var thumbUrls []string - for i := range postView.Embed.EmbedRecordWithMedia_View.Media.EmbedImages_View.Images { - thumbUrls = append(thumbUrls, postView.Embed.EmbedRecordWithMedia_View.Media.EmbedImages_View.Images[i].Thumb) - } - data["imgThumbUrls"] = thumbUrls - } else if hasMediaVideo { - if postView.Embed.EmbedRecordWithMedia_View.Media.EmbedVideo_View.Thumbnail != nil { - data["imgThumbUrls"] = []string{*postView.Embed.EmbedRecordWithMedia_View.Media.EmbedVideo_View.Thumbnail} - } - if postView.Embed.EmbedRecordWithMedia_View.Media.EmbedVideo_View.Playlist != "" { - data["videoUrl"] = postView.Embed.EmbedRecordWithMedia_View.Media.EmbedVideo_View.Playlist - data["videoType"] = "application/vnd.apple.mpegurl" - if postView.Embed.EmbedRecordWithMedia_View.Media.EmbedVideo_View.AspectRatio != nil { - data["videoWidth"] = postView.Embed.EmbedRecordWithMedia_View.Media.EmbedVideo_View.AspectRatio.Width - data["videoHeight"] = postView.Embed.EmbedRecordWithMedia_View.Media.EmbedVideo_View.AspectRatio.Height - } - } - } + if jsonld, err := buildPostJSONLD(postView, threadView.Replies, jsonldURL, hideEmbedLabels, hideReplyLabels); err == nil { + data["postJSONLD"] = jsonld + } else { + log.Warnf("failed to build post JSON-LD for %s: %v", uri, err) } return c.Render(http.StatusOK, "post.html", data) @@ -661,6 +742,44 @@ func (srv *Server) WebStarterPack(c echo.Context) error { return c.Render(http.StatusOK, "starterpack.html", data) } +// chatInviteCodeRe is a permissive sanity check on the join code so we don't +// proxy obviously-bad input to the chat appview. Codes are opaque per the +// lexicon, but in practice URL-safe and short. +var chatInviteCodeRe = regexp.MustCompile(`^[A-Za-z0-9_-]{1,64}$`) + +func (srv *Server) WebChatInvite(c echo.Context) error { + req := c.Request() + ctx := req.Context() + data := srv.NewTemplateContext() + data["noindex"] = true + data["requestURI"] = fmt.Sprintf("https://%s%s", req.Host, req.URL.Path) + + code := c.Param("code") + if !chatInviteCodeRe.MatchString(code) { + return c.Render(http.StatusOK, "chatinvite.html", data) + } + if srv.chatXrpcc == nil { + return c.Render(http.StatusOK, "chatinvite.html", data) + } + + out, err := chat.GroupGetJoinLinkPreviews(ctx, srv.chatXrpcc, []string{code}) + if err != nil { + log.Errorf("failed to fetch chat invite preview for code=%s: %v", code, err) + return c.Render(http.StatusOK, "chatinvite.html", data) + } + if len(out.JoinLinkPreviews) == 0 { + return c.Render(http.StatusOK, "chatinvite.html", data) + } + preview := out.JoinLinkPreviews[0] + + data["title"] = preview.Name + if srv.cfg.ogcardHost != "" { + // bskyogcard registers this route as /chat-invite/:code, not /chat/:code. + data["imgThumbUrl"] = fmt.Sprintf("%s/chat-invite/%s", srv.cfg.ogcardHost, code) + } + return c.Render(http.StatusOK, "chatinvite.html", data) +} + func (srv *Server) WebProfile(c echo.Context) error { ctx := c.Request().Context() data := srv.NewTemplateContext() @@ -678,20 +797,56 @@ func (srv *Server) WebProfile(c echo.Context) error { log.Warnf("failed to fetch profile for: %s\t%v", identifier, err) return c.Render(http.StatusOK, "profile.html", data) } - unauthedViewingOkay := true - for _, label := range pv.Labels { - if label.Src == pv.Did && label.Val == "!no-unauthenticated" { - unauthedViewingOkay = false - } - } + unauthedViewingOkay := !profileRequiresAuth(pv) req := c.Request() data["profileView"] = pv data["requestURI"] = fmt.Sprintf("https://%s%s", req.Host, req.URL.Path) data["requestHost"] = req.Host - if !unauthedViewingOkay { + // Prefer the handle-form URL so JSON-LD `url` and + // match. Template falls back to requestURI + // when the handle is unusable. + if url := bskyProfileURL(pv.Handle); url != "" { + data["canonicalURL"] = url + } + + // Fetch recent posts for ProfilePage.hasPart. Skipped for auth-required + // profiles (posts aren't publicly indexable anyway). Failures degrade + // gracefully — the profile still renders without hasPart. + // + // NOTE: extra XRPC call on every public profile render; consider + // caching per-profile if upstream load becomes a concern. + var recentPosts []*appbsky.FeedDefs_PostView + if unauthedViewingOkay { + af, err := appbsky.FeedGetAuthorFeed(ctx, srv.xrpcc, pv.Did, "", "posts_no_replies", false, authorFeedFetchLimit) + if err != nil { + log.Warnf("failed to fetch author feed for: %s\t%v", pv.Did, err) + } else { + for _, p := range af.Feed { + if p == nil || p.Post == nil { + continue + } + // Only the author's own posts (matches RSS handler). + if p.Post.Author == nil || p.Post.Author.Did != pv.Did { + continue + } + recentPosts = append(recentPosts, p.Post) + if len(recentPosts) >= maxRecentPosts { + break + } + } + } + } else { data["requiresAuth"] = true + data["noindex"] = true + data["nofollow"] = true + } + + if jsonld, err := buildProfileJSONLD(pv, recentPosts, hideEmbedLabels, hideReplyLabels); err == nil { + data["profileJSONLD"] = jsonld + } else { + log.Warnf("failed to build profile JSON-LD for %s: %v", pv.Did, err) } return c.Render(http.StatusOK, "profile.html", data) @@ -721,12 +876,7 @@ func (srv *Server) WebFeed(c echo.Context) error { log.Warnf("failed to fetch profile for: %s\t%v", identifier, err) return c.Render(http.StatusOK, "feed.html", data) } - unauthedViewingOkay := true - for _, label := range pv.Labels { - if label.Src == pv.Did && label.Val == "!no-unauthenticated" { - unauthedViewingOkay = false - } - } + unauthedViewingOkay := !profileRequiresAuth(pv) if !unauthedViewingOkay { return c.Render(http.StatusOK, "feed.html", data) diff --git a/bskyweb/cmd/embedr/handlers.go b/bskyweb/cmd/embedr/handlers.go index 15e57a4115..5fb4287b5a 100644 --- a/bskyweb/cmd/embedr/handlers.go +++ b/bskyweb/cmd/embedr/handlers.go @@ -95,7 +95,7 @@ func (srv *Server) parseBlueskyURL(ctx context.Context, raw string) (*syntax.ATU } var did syntax.DID if atid.IsHandle() { - ident, err := srv.dir.Lookup(ctx, *atid) + ident, err := srv.dir.Lookup(ctx, atid) if err != nil { return nil, err } diff --git a/bskyweb/go.mod b/bskyweb/go.mod index 5274340c14..e25349e947 100644 --- a/bskyweb/go.mod +++ b/bskyweb/go.mod @@ -3,7 +3,7 @@ module github.com/bluesky-social/social-app/bskyweb go 1.26 require ( - github.com/bluesky-social/indigo v0.0.0-20250729223159-573ae927246a + github.com/bluesky-social/indigo v0.0.0-20260605210604-af2fec94f34c github.com/flosch/pongo2/v6 v6.0.0 github.com/ipfs/go-log v1.0.5 github.com/joho/godotenv v1.5.1 @@ -18,11 +18,10 @@ require github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indir require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect - github.com/carlmjohnson/versioninfo v0.22.5 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect + github.com/earthboundkid/versioninfo/v2 v2.24.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -32,7 +31,6 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.5 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect - github.com/hashicorp/golang-lru/arc/v2 v2.0.6 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/ipfs/bbloom v0.0.4 // indirect github.com/ipfs/go-block-format v0.2.0 // indirect @@ -82,7 +80,6 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e // indirect - github.com/whyrusleeping/go-did v0.0.0-20230824162731-404d1707d5d6 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect @@ -93,11 +90,11 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect - golang.org/x/crypto v0.40.0 // indirect - golang.org/x/net v0.42.0 // indirect - golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/text v0.27.0 // indirect + golang.org/x/crypto v0.45.0 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/sync v0.18.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/text v0.31.0 // indirect golang.org/x/time v0.12.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/protobuf v1.36.6 // indirect diff --git a/bskyweb/go.sum b/bskyweb/go.sum index ffad3e8825..d9b4b0141a 100644 --- a/bskyweb/go.sum +++ b/bskyweb/go.sum @@ -2,12 +2,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bluesky-social/indigo v0.0.0-20250729223159-573ae927246a h1:S12KN45uIkRglMHC8PqD/Vsz0+u3KbIaBF/6rit8/Pg= -github.com/bluesky-social/indigo v0.0.0-20250729223159-573ae927246a/go.mod h1:0XUyOCRtL4/OiyeqMTmr6RlVHQMDgw3LS7CfibuZR5Q= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= -github.com/carlmjohnson/versioninfo v0.22.5 h1:O00sjOLUAFxYQjlN/bzYTuZiS0y6fWDQjMRvwtKgwwc= -github.com/carlmjohnson/versioninfo v0.22.5/go.mod h1:QT9mph3wcVfISUKd0i9sZfVrPviHuSF+cUtLjm2WSf8= +github.com/bluesky-social/indigo v0.0.0-20260605210604-af2fec94f34c h1:Jr82+1HUmwwZzDpt/eeU4sieya27iXjuPMdXZkOXoBc= +github.com/bluesky-social/indigo v0.0.0-20260605210604-af2fec94f34c/go.mod h1:JqQkz8lrOI6YZivP38GHmtVOTtzsNToITKj1gMpU5Jo= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -19,6 +15,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/earthboundkid/versioninfo/v2 v2.24.1 h1:SJTMHaoUx3GzjjnUO1QzP3ZXK6Ee/nbWyCm58eY3oUg= +github.com/earthboundkid/versioninfo/v2 v2.24.1/go.mod h1:VcWEooDEuyUJnMfbdTh0uFN4cfEIg+kHMuWB2CDCLjw= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flosch/pongo2/v6 v6.0.0 h1:lsGru8IAzHgIAw6H2m4PCyleO58I40ow6apih0WprMU= @@ -48,8 +46,6 @@ github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/golang-lru/arc/v2 v2.0.6 h1:4NU7uP5vSoK6TbaMj3NtY478TTAWLso/vL1gpNrInHg= -github.com/hashicorp/golang-lru/arc/v2 v2.0.6/go.mod h1:cfdDIX05DWvYV6/shsxDfa/OVcRieOt+q4FnM8x+Xno= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= @@ -211,8 +207,6 @@ github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSD github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e h1:28X54ciEwwUxyHn9yrZfl5ojgF4CBNLWX7LR0rvBkf4= github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so= -github.com/whyrusleeping/go-did v0.0.0-20230824162731-404d1707d5d6 h1:yJ9/LwIGIk/c0CdoavpC9RNSGSruIspSZtxG3Nnldic= -github.com/whyrusleeping/go-did v0.0.0-20230824162731-404d1707d5d6/go.mod h1:39U9RRVr4CKbXpXYopWn+FSH5s+vWu6+RmguSPWAq5s= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -253,8 +247,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -272,16 +266,16 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -297,8 +291,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -310,8 +304,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/bskyweb/templates/base.html b/bskyweb/templates/base.html index e33479e697..7f723ce36c 100644 --- a/bskyweb/templates/base.html +++ b/bskyweb/templates/base.html @@ -139,6 +139,7 @@ + {% if noindex %}{% endif %} diff --git a/bskyweb/templates/chatinvite.html b/bskyweb/templates/chatinvite.html new file mode 100644 index 0000000000..63ae03ee39 --- /dev/null +++ b/bskyweb/templates/chatinvite.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} + +{% block html_head_extra -%} + {%- if requestURI %} + + {% endif -%} + {%- if imgThumbUrl %} + + + {%- else -%} + + + {% endif -%} + + {%- if title %} + + + {%- else -%} + + + {% endif -%} + + + +{%- endblock %} diff --git a/bskyweb/templates/post.html b/bskyweb/templates/post.html index 3e9fb59e13..72ac4664e7 100644 --- a/bskyweb/templates/post.html +++ b/bskyweb/templates/post.html @@ -15,8 +15,13 @@ {%- if requestURI %} + {% if canonicalURL %} + + + {% else %} + {% endif %} {% endif -%} {%- if postView.Author.DisplayName %} @@ -34,6 +39,11 @@ {% endfor %} + {% else %} + + + + {% endif %} {%- if videoUrl %} @@ -42,11 +52,6 @@ {% endif -%} {% endif -%} - {% else %} - - - - {% endif %} {%- if postView.LikeCount %} @@ -64,52 +69,20 @@ - + {%- if postJSONLD %} + + {% endif -%} {%- elif requiresAuth and profileHandle -%} {%- if requestURI %} + {% if canonicalURL %} + + + {% else %} + {% endif %} {% endif -%} {%- if profileDisplayName %} diff --git a/bskyweb/templates/profile.html b/bskyweb/templates/profile.html index b4be429289..804ee7a92b 100644 --- a/bskyweb/templates/profile.html +++ b/bskyweb/templates/profile.html @@ -12,8 +12,13 @@ {%- if requestURI %} + {% if canonicalURL %} + + + {% else %} + {% endif %} {% endif -%} {%- if profileView -%} @@ -52,44 +57,9 @@ {% endif %} - + {%- if profileJSONLD %} + + {% endif -%} {% endif -%} {%- endblock %} diff --git a/conductor.json b/conductor.json index ef5c740b55..f52ec0200a 100644 --- a/conductor.json +++ b/conductor.json @@ -1,6 +1,6 @@ { "scripts": { - "setup": "yarn install", - "run": "yarn web --port $CONDUCTOR_PORT" + "setup": "pnpm install --frozen-lockfile", + "run": "pnpm web --port $CONDUCTOR_PORT" } } \ No newline at end of file diff --git a/dev-env/mock-server.ts b/dev-env/mock-server.ts index 76a636f6db..12f5bb7edd 100644 --- a/dev-env/mock-server.ts +++ b/dev-env/mock-server.ts @@ -1,7 +1,7 @@ import {createServer as createHTTPServer} from 'node:http' import {parse} from 'node:url' -import {createServer, type TestPDS} from './test-pds' +import {createServer, type TestPDS} from './test-pds.ts' let server: TestPDS // eslint-disable-next-line @typescript-eslint/no-misused-promises @@ -97,6 +97,7 @@ createHTTPServer(async (req, res) => { await server.mocker.follow('alice', 'bob') await server.mocker.follow('alice', 'carla') console.log('Generating mock posts') + // eslint-disable-next-line @typescript-eslint/no-explicit-any let posts: Record = { alice: [], bob: [], diff --git a/dev-env/package.json b/dev-env/package.json index 40c9d469eb..58fe9a5d87 100644 --- a/dev-env/package.json +++ b/dev-env/package.json @@ -1,12 +1,16 @@ { "name": "dev-env", "version": "0.0.0", + "type": "module", "scripts": { - "start": "NODE_ENV=development ./dev-infra/with-test-redis-and-db.sh ts-node ./mock-server.ts" + "start": "NODE_ENV=development ./dev-infra/with-test-redis-and-db.sh node ./mock-server.ts" }, "dependencies": { - "@atproto/dev-env": "^0.3.215", - "ts-node": "^10.9.2", - "typescript": "^6.0.2" + "@atproto/api": "^0.20.0", + "@atproto/dev-env": "^0.5.0", + "typescript": "^6.0.3" + }, + "devDependencies": { + "@types/node": "^25.8.0" } } diff --git a/dev-env/pnpm-lock.yaml b/dev-env/pnpm-lock.yaml new file mode 100644 index 0000000000..572a128c5f --- /dev/null +++ b/dev-env/pnpm-lock.yaml @@ -0,0 +1,4447 @@ +--- +lockfileVersion: '9.0' + +importers: + + .: + configDependencies: {} + packageManagerDependencies: + '@pnpm/exe': + specifier: 11.1.3 + version: 11.1.3 + pnpm: + specifier: 11.1.3 + version: 11.1.3 + +packages: + + '@pnpm/exe@11.1.3': + resolution: {integrity: sha512-J6bSMpZlHVUKuMKtPT/+lrFPpvBiOIgk6HnNC3vmoM/fsMgFhao6ITFwdsUMzdCl2qHf9cnVYA4ZBdsGmVUnpg==} + hasBin: true + + '@pnpm/linux-arm64@11.1.3': + resolution: {integrity: sha512-sz3fc0hSqguk2eGe/InelpBD3LP82MzF+8pLzDpYNGuhasGY+VWkuxEo02HczQYRVXTsbpZVepk+Qs2CONc04Q==} + cpu: [arm64] + os: [linux] + + '@pnpm/linux-x64@11.1.3': + resolution: {integrity: sha512-UadJh5fJZWa47OtdZTWLKWDj4z5WpZFB5pS2wOh/kfKypUmQyjmbOMClP7/yJGS2ZtrSjRgYjIEWAjndkWsMaA==} + cpu: [x64] + os: [linux] + + '@pnpm/linuxstatic-arm64@11.1.3': + resolution: {integrity: sha512-lWmGr96w+VrIRVsEfTXROB3GeQNxrX2Hy32j3USHr6WlqmpH1i7YjavJGpoXeVZbb77fCFjNFAtsJzGXSgy/Og==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@pnpm/linuxstatic-x64@11.1.3': + resolution: {integrity: sha512-I74GDBOPbr5TXgob3ct4hv6BQtLGdsjGJrII2qNl/e2xYHXekjIWE4Eh7RGN4C7xu+BZYKtvnOOr149yR7KxUw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@pnpm/macos-arm64@11.1.3': + resolution: {integrity: sha512-nWn155BVa54iNyg4iolVhjMtqumXHPh8ul5CFjQFJXdwgr9MRUgc5EUBML1+7mS8C/7Wcex5HUgn/w3fliHCsQ==} + cpu: [arm64] + os: [darwin] + + '@pnpm/win-arm64@11.1.3': + resolution: {integrity: sha512-4u6PQL7/WgwdweC2ZJcdCzhM1koRFG5ofcUSrIVsdyo9ePfGq2D9p1rkZbiLEfIGvl3GKOsfMB5DRTgs8es4AQ==} + cpu: [arm64] + os: [win32] + + '@pnpm/win-x64@11.1.3': + resolution: {integrity: sha512-JmhH7ljJ3MWjvWFz/YHbu/27ISCNLZsQb1JG+ib3uHlfUwIJuF6BudXYsbS5Uu0o7xUvC9sPPm+Uho4Or6R3vA==} + cpu: [x64] + os: [win32] + + '@reflink/reflink-darwin-arm64@0.1.19': + resolution: {integrity: sha512-ruy44Lpepdk1FqDz38vExBY/PVUsjxZA+chd9wozjUH9JjuDT/HEaQYA6wYN9mf041l0yLVar6BCZuWABJvHSA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@reflink/reflink-darwin-x64@0.1.19': + resolution: {integrity: sha512-By85MSWrMZa+c26TcnAy8SDk0sTUkYlNnwknSchkhHpGXOtjNDUOxJE9oByBnGbeuIE1PiQsxDG3Ud+IVV9yuA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@reflink/reflink-linux-arm64-gnu@0.1.19': + resolution: {integrity: sha512-7P+er8+rP9iNeN+bfmccM4hTAaLP6PQJPKWSA4iSk2bNvo6KU6RyPgYeHxXmzNKzPVRcypZQTpFgstHam6maVg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@reflink/reflink-linux-arm64-musl@0.1.19': + resolution: {integrity: sha512-37iO/Dp6m5DDaC2sf3zPtx/hl9FV3Xze4xoYidrxxS9bgP3S8ALroxRK6xBG/1TtfXKTvolvp+IjrUU6ujIGmA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@reflink/reflink-linux-x64-gnu@0.1.19': + resolution: {integrity: sha512-jbI8jvuYCaA3MVUdu8vLoLAFqC+iNMpiSuLbxlAgg7x3K5bsS8nOpTRnkLF7vISJ+rVR8W+7ThXlXlUQ93ulkw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@reflink/reflink-linux-x64-musl@0.1.19': + resolution: {integrity: sha512-e9FBWDe+lv7QKAwtKOt6A2W/fyy/aEEfr0g6j/hWzvQcrzHCsz07BNQYlNOjTfeytrtLU7k449H1PI95jA4OjQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@reflink/reflink-win32-arm64-msvc@0.1.19': + resolution: {integrity: sha512-09PxnVIQcd+UOn4WAW73WU6PXL7DwGS6wPlkMhMg2zlHHG65F3vHepOw06HFCq+N42qkaNAc8AKIabWvtk6cIQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@reflink/reflink-win32-x64-msvc@0.1.19': + resolution: {integrity: sha512-E//yT4ni2SyhwP8JRjVGWr3cbnhWDiPLgnQ66qqaanjjnMiu3O/2tjCPQXlcGc/DEYofpDc9fvhv6tALQsMV9w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@reflink/reflink@0.1.19': + resolution: {integrity: sha512-DmCG8GzysnCZ15bres3N5AHCmwBwYgp0As6xjhQ47rAUTUXxJiK+lLUxaGsX3hd/30qUpVElh05PbGuxRPgJwA==} + engines: {node: '>= 10'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + pnpm@11.1.3: + resolution: {integrity: sha512-yFNX/hfKEt0j3XBxgiZm39fjy3b+IU4zcLXqL7NPKiMRhVCbY+cX880KyzjdP42CvNXoFyQArmeLcOpPvtCJbQ==} + engines: {node: '>=22.13'} + hasBin: true + +snapshots: + + '@pnpm/exe@11.1.3': + dependencies: + '@reflink/reflink': 0.1.19 + detect-libc: 2.1.2 + optionalDependencies: + '@pnpm/linux-arm64': 11.1.3 + '@pnpm/linux-x64': 11.1.3 + '@pnpm/linuxstatic-arm64': 11.1.3 + '@pnpm/linuxstatic-x64': 11.1.3 + '@pnpm/macos-arm64': 11.1.3 + '@pnpm/win-arm64': 11.1.3 + '@pnpm/win-x64': 11.1.3 + + '@pnpm/linux-arm64@11.1.3': + optional: true + + '@pnpm/linux-x64@11.1.3': + optional: true + + '@pnpm/linuxstatic-arm64@11.1.3': + optional: true + + '@pnpm/linuxstatic-x64@11.1.3': + optional: true + + '@pnpm/macos-arm64@11.1.3': + optional: true + + '@pnpm/win-arm64@11.1.3': + optional: true + + '@pnpm/win-x64@11.1.3': + optional: true + + '@reflink/reflink-darwin-arm64@0.1.19': + optional: true + + '@reflink/reflink-darwin-x64@0.1.19': + optional: true + + '@reflink/reflink-linux-arm64-gnu@0.1.19': + optional: true + + '@reflink/reflink-linux-arm64-musl@0.1.19': + optional: true + + '@reflink/reflink-linux-x64-gnu@0.1.19': + optional: true + + '@reflink/reflink-linux-x64-musl@0.1.19': + optional: true + + '@reflink/reflink-win32-arm64-msvc@0.1.19': + optional: true + + '@reflink/reflink-win32-x64-msvc@0.1.19': + optional: true + + '@reflink/reflink@0.1.19': + optionalDependencies: + '@reflink/reflink-darwin-arm64': 0.1.19 + '@reflink/reflink-darwin-x64': 0.1.19 + '@reflink/reflink-linux-arm64-gnu': 0.1.19 + '@reflink/reflink-linux-arm64-musl': 0.1.19 + '@reflink/reflink-linux-x64-gnu': 0.1.19 + '@reflink/reflink-linux-x64-musl': 0.1.19 + '@reflink/reflink-win32-arm64-msvc': 0.1.19 + '@reflink/reflink-win32-x64-msvc': 0.1.19 + + detect-libc@2.1.2: {} + + pnpm@11.1.3: {} + +--- +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@atproto/api': + specifier: ^0.20.0 + version: 0.20.5 + '@atproto/dev-env': + specifier: ^0.5.0 + version: 0.5.3 + typescript: + specifier: ^6.0.3 + version: 6.0.3 + devDependencies: + '@types/node': + specifier: ^25.8.0 + version: 25.9.1 + +packages: + + '@atproto-labs/did-resolver@0.3.0': + resolution: {integrity: sha512-6w8UxJQVj40TqKJm+D5s6pcH45eWH/KvfAR+gYC6KOux/mM7ZI3IKOhI1Gq7GIte1eOp9WXwASN1RQAFYZ4MFw==} + engines: {node: '>=22'} + + '@atproto-labs/fetch-node@0.3.0': + resolution: {integrity: sha512-Dj4OlW8E7Hyk0vpJ31ThUp5PCXMVi2iwPhavWkiGhjLx+pAhcEsIEnvV2cf+cGJcYKXczLwIl1agoLUMSZq1ag==} + engines: {node: '>=22'} + + '@atproto-labs/fetch@0.3.0': + resolution: {integrity: sha512-yZhNsRZyqhjmzHlXmE9k6XU0f6+ynfNyrHhB4FlEyUjkBvf5vNLjdJyBq+Jp7ISGMigA1fn2lAgaE/qkrVo9iw==} + engines: {node: '>=22'} + + '@atproto-labs/pipe@0.2.0': + resolution: {integrity: sha512-89X6gv+8/SiZYwJ4+Hw8iaU1WymbElZO6LsMgHd+c1XFs/78KhsIL4xp0RCs7/W8izK9BxaTEnYN5csRmwQ99Q==} + engines: {node: '>=22'} + + '@atproto-labs/simple-store-memory@0.2.0': + resolution: {integrity: sha512-nDZIS6vOBvR8NqObGmj0GFSdz3N6gyQ7ny3l/uiezwuCWShsxtA9OkYcjaQ7Kw3hydXTe4iEGu/BDOACncnxDg==} + engines: {node: '>=22'} + + '@atproto-labs/simple-store-redis@0.1.0': + resolution: {integrity: sha512-VqDrJycSPKnJol/KQwsp0wQyPsVyin8bnwcSUB9Um5HPqJ4p9VK2dkMxuJ1u4qW5JXN5uVQEDHs8Spgekgv3SA==} + engines: {node: '>=22'} + peerDependencies: + ioredis: ^5.3.2 + + '@atproto-labs/simple-store@0.4.0': + resolution: {integrity: sha512-sbPEju2QXzN6lGWfr5p2Kg82ZeaG1UyVFOrMQc4eCjUvxGOjIxuYOVIiTuPTFOC5Rsvsb8jMOmLjLi8FR/CMYw==} + engines: {node: '>=22'} + + '@atproto-labs/xrpc-utils@0.1.0': + resolution: {integrity: sha512-nmjM83KucbRnz/CDSXQX6FLH/GP93XjcI1u5WFlomPscmeOAP1+9AOVsMQlKmh27I3hgaSOdCr03GJjAIpuPZQ==} + engines: {node: '>=22'} + + '@atproto/api@0.20.5': + resolution: {integrity: sha512-VqkRYKR9vRRk36NhtytJz5TPhNtR1hczCcfM+bWoOK6MBvWUT8HwelufrycFAIbFIH7n4ws+5UbnLYpTIBQZ6Q==} + engines: {node: '>=22'} + + '@atproto/aws@0.3.0': + resolution: {integrity: sha512-QFcemMMHyHmIs9zipvp1yq0x0e/Jhm9T5t2gwl9PNFiUqJfJbid5GQqR5sHwQ/3gaQceUmYfUeGGFH0Zb42DGw==} + engines: {node: '>=22'} + + '@atproto/bsky@0.0.234': + resolution: {integrity: sha512-mw/wvd26Nn26RxAVPh9wSAf2u+mh37PaDMvfqDiQLcgYnc+RjH8enLd4twOmp++VOv6agoMqhhCGzOHCOv3PwA==} + engines: {node: '>=22'} + + '@atproto/bsync@0.0.27': + resolution: {integrity: sha512-PZ/0otQV2k5ZHFQb+uR2ivNWS+I8UsPPj/e7r95xJy1vZCN3vcxgtx87rLzEjTLF3GiuQbOltG7GVMy7ZV7vAQ==} + engines: {node: '>=22'} + + '@atproto/common-web@0.5.0': + resolution: {integrity: sha512-ReWnkuZdDU/74/I47gaI26uxQjHmpq4edp41NnZZQ5vIIKGb7Ei6pZHzDTUD9JURo109SKrPx9RMP2IQm0fOKA==} + engines: {node: '>=22'} + + '@atproto/common@0.1.0': + resolution: {integrity: sha512-OB5tWE2R19jwiMIs2IjQieH5KTUuMb98XGCn9h3xuu6NanwjlmbCYMv08fMYwIp3UQ6jcq//84cDT3Bu6fJD+A==} + + '@atproto/common@0.1.1': + resolution: {integrity: sha512-GYwot5wF/z8iYGSPjrLHuratLc0CVgovmwfJss7+BUOB6y2/Vw8+1Vw0n9DDI0gb5vmx3UI8z0uJgC8aa8yuJg==} + + '@atproto/common@0.6.1': + resolution: {integrity: sha512-mXR4R8nYFgs4gncFXNyIcxMcjuvpeCn3spu4pIlfWsri+MxXPr4jHWkjyv+b2JIvI+nns3L3ElFKWPEO4GlZdA==} + engines: {node: '>=22'} + + '@atproto/crypto@0.1.0': + resolution: {integrity: sha512-9xgFEPtsCiJEPt9o3HtJT30IdFTGw5cQRSJVIy5CFhqBA4vDLcdXiRDLCjkzHEVbtNCsHUW6CrlfOgbeLPcmcg==} + + '@atproto/crypto@0.5.0': + resolution: {integrity: sha512-HBfq6z+tzkiBzPhHOvRFkQU3inTQIUJAkb1UEyGx+pdPTnFFtnfIoo9Kr1ZV/FzMIgnHOA8H2UJC3dxxFl1eVg==} + engines: {node: '>=22'} + + '@atproto/dev-env@0.5.3': + resolution: {integrity: sha512-yRk9nBtD6h8pr4u/5+2Y3eJxQF2fGRiE3SphE71sUYtuPqUYoBsBZZTwPsucF0V5IwAczWLn0fEn3JhpKr6Ykw==} + engines: {node: '>=22'} + hasBin: true + + '@atproto/did@0.4.0': + resolution: {integrity: sha512-OADX1NXCkScMkNJmSQnTUfPoDJ2IoOUTtqVfuY4z+a8//JDIsY6FDygv3GR8Sh2laz5qodlCA7XM+u6XVine3g==} + engines: {node: '>=22'} + + '@atproto/identity@0.5.0': + resolution: {integrity: sha512-9KjpOVhwIMh1CDC3ErcxOof9mfYz+dZrUFRdp1jcaY8xZo4CkQgGBDKG2/6dP1FP4iqfG6r52JGhnTdcCzwBwQ==} + engines: {node: '>=22'} + + '@atproto/jwk-jose@0.2.0': + resolution: {integrity: sha512-emI0XXWoZvcy64Q1NnVictPtez3p2jWUU5TMHxalIqo917z9QEJ/aMcso6X9YPfnbL8sa1n6VGo+5THBMobZzg==} + engines: {node: '>=22'} + + '@atproto/jwk@0.7.0': + resolution: {integrity: sha512-HJ0crIKTbeTIiXCodgcgpaTcfKmeUE4IawjpoiVav1KFiIrD8ML2c7NdLETo8XnHuuj6DNzCtfD/381od/QjkA==} + engines: {node: '>=22'} + + '@atproto/lex-builder@0.1.2': + resolution: {integrity: sha512-cBkuJiBkiIO6KzO2AGTc/0EOJKRfBxWwAQbmy4WYnNO3ewC/8BztQuAxY99mAf/NWuksEKXL+UY6J/ZEKLK1pA==} + engines: {node: '>=22'} + + '@atproto/lex-cbor@0.1.0': + resolution: {integrity: sha512-SGL7BHPYN0OmvtnVixieYWyh2ghxI7iHADTLfz52Xp5zR+BZY3QggIKJ32KfyYN9Ss2tduaqsY5aclEki2Aq0A==} + engines: {node: '>=22'} + + '@atproto/lex-client@0.1.2': + resolution: {integrity: sha512-ZZXaqRWeVjRrUoc/7Of3gP2KN7z7Sb8OnNT462ZIUlruxZfrLz9roFUr6jHOpnEIR+5xNk5fnHPAD8KSl7KkXw==} + engines: {node: '>=22'} + + '@atproto/lex-data@0.1.1': + resolution: {integrity: sha512-/xza8nU/YhtzhETnHL3QKKofaJ28/0NCzhT7LaYoUkm8EgypWp5ykEtmW52yLhQM2JF6fVa25g1soQmNTGqtSg==} + engines: {node: '>=22'} + + '@atproto/lex-document@0.1.0': + resolution: {integrity: sha512-I2q2iwK8RnSHkBeAj5xdJNYdHiUQqlkbbleBSJKJXlYOYy5y86dWRo9IfE0l9E3qZ3/zvy1ydlEZmZupEt0FGg==} + engines: {node: '>=22'} + + '@atproto/lex-installer@0.1.0': + resolution: {integrity: sha512-NXrJNe2qY4YreC82VgPE7stXnzSnI+xMeNXvgJDiMZj4MeSQeQn5ElFVd1yzRRV9sZREf6Y/HE+y1ffb2uewXA==} + engines: {node: '>=22'} + + '@atproto/lex-json@0.1.0': + resolution: {integrity: sha512-oWUrRMwFyWpmi/5k1Se3xBTbP06XdxBS5iFuUz9LmqItaPXwrWRD87a9ldPvINQ/A2/mn7J6/qug8sDVlhD+vQ==} + engines: {node: '>=22'} + + '@atproto/lex-resolver@0.1.0': + resolution: {integrity: sha512-zliMiRW4ttSNFreKxyvSpaYQjeWrKpV/lutnXI9BNE8Ysgnw8Rltm0bR7kG/y0OlyClxhhU/vAG+OR8NpjhU1Q==} + engines: {node: '>=22'} + + '@atproto/lex-schema@0.1.1': + resolution: {integrity: sha512-/PX8q2Pd5PT9ozb9Pvw6gRifsGIU1LA/ccnu0IMyvstpCeAYyvdN6y0iHlUrzN0iSNTwStfA9H3hVaVaeZyDVQ==} + engines: {node: '>=22'} + + '@atproto/lex@0.1.2': + resolution: {integrity: sha512-s4Z29QUcnsKPCJeN9VZiCQIpP9vklm/6EoO7znsUDNN4jZknvnd8vrJbCV9HQRHM8C6ZuHxh7vtsjlE8tzxnyw==} + engines: {node: '>=22'} + hasBin: true + + '@atproto/lexicon@0.7.1': + resolution: {integrity: sha512-voNfNED5KUxn3vpo7N5DMRblBDfWf7kSfdKhJFC1RrLCxg38YbBzzURNVQJ32bp13Oot8kYfyXBWxTgtKLvw8w==} + engines: {node: '>=22'} + + '@atproto/oauth-provider-api@0.6.0': + resolution: {integrity: sha512-wFioPBgI71v4PuEwYmIYPp4GNOyaRw1UX8FJq0AiNuUGMbqCjdd7ImSsG4b43hsHr5wOsMirsWyccjUEdhb+yQ==} + engines: {node: '>=22'} + + '@atproto/oauth-provider-ui@0.7.0': + resolution: {integrity: sha512-piiKxZ6qyRNp/dEMxB/k1TD+3dVS7WEmLFY2r5AB3ACdvJcZd63ARIrX03AJkh8MVTwActDrosmQEDxn/2ddRw==} + engines: {node: '>=22'} + + '@atproto/oauth-provider@0.18.0': + resolution: {integrity: sha512-qoAvmZ9Owrct8rgdw7YOeVKclZzFw/q93mYxnqDHmzemcMjNbyXlYnhEJ2ZtQENT/AH7DJdkdz+7rGhj6uO9ow==} + engines: {node: '>=22'} + + '@atproto/oauth-scopes@0.4.0': + resolution: {integrity: sha512-0WLvgXkm2mTO4rBZbyXQab6CHyxCJwn7Y6PeVEQ4mKwrEY4lBJ+zbjOxOh2cRQ6vYkCmMk63GRUs65NPzHFlug==} + engines: {node: '>=22'} + + '@atproto/oauth-types@0.7.0': + resolution: {integrity: sha512-4o1pRBTnZrIqaXH3jg9K2XwkTlRb7ohp2BArQ1gNPYp2znrRiELmwjxqR3CH5KD1Dz64kJXoEkbeLKl0devkNw==} + engines: {node: '>=22'} + + '@atproto/ozone@0.1.176': + resolution: {integrity: sha512-uxKQS1twD1tASH5XOI1KiDsUZFx3GlO47ozJ3LP3Wj4Q7CqJH4h7QDgRGMClxZVRJAYRnA9hc6/omJQZ1UyXIg==} + engines: {node: '>=22'} + + '@atproto/pds@0.5.0': + resolution: {integrity: sha512-srMvxWNZFs8ttZvNl+g4vlsWWsa7dulfCHMs3JUhLy3DcKyBYEfiw/NXG9arDtA/+kb1lh+to3FtO1itY4oWFg==} + engines: {node: '>=22'} + + '@atproto/repo@0.10.0': + resolution: {integrity: sha512-dGnU3R+geXepOZFIn3YUu6WaXLuT1NxqNvusR/JmAqPjjT6dT4tjouZeDPrumSnZm2f5Z8xMeDfVxpMWQ4stFw==} + engines: {node: '>=22'} + + '@atproto/sync@0.3.1': + resolution: {integrity: sha512-oZKfdh/vzR9KqwSy8j5j5MmlHWFHpxy0AVNqeUai1vIpKW+Z7bkCEpts98dH0NGkIjBIkeon4lr0zxlERfR0pg==} + engines: {node: '>=22'} + + '@atproto/syntax@0.6.1': + resolution: {integrity: sha512-kA4dQDoMPpWCH8N0Q4KoSq024u5MkVfDVa8DdhyLjGA72z/khbOf1jXKPv7NIL2oEc9aj7geKELdvqyf4ogopA==} + engines: {node: '>=22'} + + '@atproto/ws-client@0.1.0': + resolution: {integrity: sha512-8qG+A+htxEHpDJRgtYNUExUOejDwU4nE2SEJCFLiTc1G+s0GbFaeg7Fk5drQA9tLMHQWoU9uqIr1YQpJyvJs8w==} + engines: {node: '>=22'} + + '@atproto/xrpc-server@0.11.1': + resolution: {integrity: sha512-h68Mb+Aa1R4tMF/F1BTV5EeBA31wK+Fsn9LoNrNkfeFh5Bxj4U2ZnnTZTodqtJlbZzXbhUAAKtws3XpN7tdX5w==} + engines: {node: '>=22'} + + '@atproto/xrpc@0.8.0': + resolution: {integrity: sha512-NJy02bIKrWlE2NQkRV1kT0Cj0ixbuxlF/MejBdo4cPWAa9v3oZexvAcjjb0zaOYeABkaU14iyIhvn2G4e/oLpw==} + engines: {node: '>=22'} + + '@aws-crypto/crc32@5.2.0': + resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/crc32c@5.2.0': + resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==} + + '@aws-crypto/sha1-browser@5.2.0': + resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==} + + '@aws-crypto/sha256-browser@5.2.0': + resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} + + '@aws-crypto/sha256-js@5.2.0': + resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/supports-web-crypto@5.2.0': + resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} + + '@aws-crypto/util@5.2.0': + resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} + + '@aws-sdk/client-cloudfront@3.1053.0': + resolution: {integrity: sha512-M/pPSPcAOSuL/gQvMId/m5AFzExVPMzS0CqZEcXDjVP7AGBSJ9NxsBV4Sg2/jaC/kXodbxLXdxWkXMXzJZmQXA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/client-kms@3.1053.0': + resolution: {integrity: sha512-U7dr+jGnAV2UpQesHBan4lgI8TAgFyWhvcWAon8Ub4UjIQz5RDbP5eyU7DU2qjmgGe7iL0U0/jYvQpBj2TkelA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/client-s3@3.1053.0': + resolution: {integrity: sha512-/oGxoB6p1Nqs935Blt+v1o+anSCEf2n3RjIrcLz84i4cn2Gr+Z7JpDdUkG5+74r5ctqEPG7k/phTGbJ9fNKnHg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/core@3.974.13': + resolution: {integrity: sha512-+Y5/4tHki0uYgyx8eun146DegRVQBpdKGK5RbV0FTKJPpaKTchvqVxrrRFK6Wk0JksO4iAZKw3eqxGEIwtO98w==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/crc64-nvme@3.972.9': + resolution: {integrity: sha512-P+QGozmXn2mZZI7sDgk+aUm+RTI61MPSFB+Ir2vjEjEbEsE4e7hYtzrDvAUxZy9ko81h53e11+F/GYlvwDkaOQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-env@3.972.39': + resolution: {integrity: sha512-29wX9zpAvEt1vcj0psha+y6ygBHy2V/S72mp6e7q0KARLWXq+pwE/lR6qGkwknQvruh52lXvlqZIga8Hdxkucw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-http@3.972.41': + resolution: {integrity: sha512-IA3CQTjtJkb6u1H4mE4936c8OPBMa9Jggtwe8U2Mqw/vvb/tZ5Ebd0mcZcX0uKWQhOyYo/+qNIwkV5Xh+FeJJA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-ini@3.972.43': + resolution: {integrity: sha512-4mzII+3mZEVXXE1xzrLQrCJL7/r62A63bA6SVzZoNL5rqCJghpf+xgGltVrIBBs0n+mOZBKrQl2tRREtvZ5l6A==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-login@3.972.43': + resolution: {integrity: sha512-HG7kQCwXtbv3oBV61Ins0oNX8KKyvrMqqRkb6ZiAfQHbMuHaiNaEb2KnpKLPkNpqImSBK82UkVE/kaY6IfWikA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-node@3.972.44': + resolution: {integrity: sha512-sDaBIT0yrNNIPfvlsiTCmANm07zKju+ipWODjEXgZlsjMeIJR3LVp7RDyAOzUoAsTbDfYKDWp+i5WrFiQP6rmQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-process@3.972.39': + resolution: {integrity: sha512-2k/amBifLd75eXNwgvPw/2lKYSQ3NhvHQgkVKVjfUq13/eJ3JRtHmznuFenn74OK3sSfp4SMy1YB2w+UVXoKqA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-sso@3.972.43': + resolution: {integrity: sha512-LPc3+Y4vhH1T4x6CMqwCM6hk5+SRf/Lwmgm8INm95wxTtIRHcMwQUVkDzWu4Iw/RSncxYM2BC01OrYbxOPZvyg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.972.43': + resolution: {integrity: sha512-wQtL34lUD/09VXjwAUo2T+I3aEXRDxMB3DKmTJL/Zj0Gi6sLDTrVhae1XVt01yzkquOWajI/sZW72JGDZ1ciTw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/lib-storage@3.879.0': + resolution: {integrity: sha512-FAb3vOfLIrf8lPuDoxKRu18DxXfQLEFm7MoXi0jd8ooFjD09jpVCQGNrRuMCqc688wrx7zJSovWObtn4LRjvrg==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@aws-sdk/client-s3': ^3.879.0 + + '@aws-sdk/middleware-bucket-endpoint@3.972.15': + resolution: {integrity: sha512-O2HDANa+MrvbxpaRVQDiH3T13uAa9AkMjKyZmDygwauAmmvqZ5B0iRmKW+fuVGW6NPXuyXurFgIx69lSvmAWGA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-expect-continue@3.972.13': + resolution: {integrity: sha512-sHiqIFg8o2ipT7t40B89Vj0ubSUtY6OSt/+Ee/OXhHch5K4+81zP2+QX8Lkc/nJ2QSmCySxOke7TEbmX69fe2g==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-flexible-checksums@3.974.21': + resolution: {integrity: sha512-alAu9heyiBK/OmRNXVxq8mmPTgeW2AQ6EYjRsI38kPZa1MZvt2Jh+BlGq7/GG9OVXOaEgD7DlGj/Lzfy5OmuEg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-location-constraint@3.972.11': + resolution: {integrity: sha512-hkfspNUP4criAH6ton6BGKgnm5dZx+7bUOy1YqlTfejDeUPAM23D81q/IX+hdlS3KUsfwGz5ADTqZWKBEUpf4A==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-sdk-s3@3.972.42': + resolution: {integrity: sha512-/xNqNGXv9LaxZd25L9VV4pnSOw9OdDNO4rAHamM+h3KQBSITljIH9vk3dveGga1I2j36lQd0rdG3gjNEXvtNew==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-ssec@3.972.11': + resolution: {integrity: sha512-7PQvGNhtveKlvVqNahqWx5yrwxP7ecwAoB1dYBf8eKwfo2tzzCbNnW+q2nO3N066ktQaB4iBQbDRWtizm+amoQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/nested-clients@3.997.11': + resolution: {integrity: sha512-nWXXJ1r/r8N2Gw1pWolRgED38/A9A8DHR2ETWIv220zh4PZHcybbR4hUVWWktmNXTRHzDJwRluapHn0rZxuoqA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.996.28': + resolution: {integrity: sha512-qs9z5LqXO/CZC2Lg9SGKpoLU8Rhi+m2pFKZqfO9pytX1clc0katqtsDNupJxFy0xT9wsZSPzM2v1y+/H/zfp5Q==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.1052.0': + resolution: {integrity: sha512-QqZNB3so7UIDxZtroc85TQaLVxdZRFm0eWM1CSR2N+b06as9TOrilvrlTZuj3guYlxMs6yLOgGxnklJ5qMYtTw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/types@3.973.9': + resolution: {integrity: sha512-kuBfgQVdcz5Bmapc4A13YbpVw/pXkesfhetcFYwbntqas8sF41OHyd4o28+/TG2ZQdHBsv90Lsu5y6oitvYCdg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-locate-window@3.965.5': + resolution: {integrity: sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/xml-builder@3.972.25': + resolution: {integrity: sha512-GH+Kjz4nPKWKHnsiQpnhP1MJdTGIcK4rAka6tzakgjjUkVgNsmPeEbbRAf09SzS1hjGu6duGHCBsxYke0BhHjQ==} + engines: {node: '>=20.0.0'} + + '@aws/lambda-invoke-store@0.2.4': + resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==} + engines: {node: '>=18.0.0'} + + '@bufbuild/protobuf@1.10.1': + resolution: {integrity: sha512-wJ8ReQbHxsAfXhrf9ixl0aYbZorRuOWpBNzm8pL8ftmSxQx/wnJD5Eg861NwJU/czy2VXFIebCeZnZrI9rktIQ==} + + '@connectrpc/connect-express@1.7.0': + resolution: {integrity: sha512-Ajj/jupbF0nCgYlUzuKWQHuwLq5XKqQdJYF5aHmnVZlUcg5Z5zp1u4waG2j4JRczuTb8e0R1yVMTnad5SHM3Lg==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@bufbuild/protobuf': ^1.10.0 + '@connectrpc/connect': 1.7.0 + '@connectrpc/connect-node': 1.7.0 + + '@connectrpc/connect-node@1.7.0': + resolution: {integrity: sha512-6vaPIkG/NyhxlYgytLoR9KYbPhczEboFB2OYWkA9qvUz1K7efXfeGrlRxoLtpa+r8VxyIOw73w5ktNe743nD+A==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@bufbuild/protobuf': ^1.10.0 + '@connectrpc/connect': 1.7.0 + + '@connectrpc/connect@1.7.0': + resolution: {integrity: sha512-iNKdJRi69YP3mq6AePRT8F/HrxWCewrhxnLMNm0vpqXAR8biwzRtO6Hjx80C6UvtKJ5sFmffQT7I4Baecz389w==} + peerDependencies: + '@bufbuild/protobuf': ^1.10.0 + + '@did-plc/lib@0.0.1': + resolution: {integrity: sha512-RkY5w9DbYMco3SjeepqIiMveqz35exjlVDipCs2gz9AXF4/cp9hvmrp9zUWEw2vny+FjV8vGEN7QpaXWaO6nhg==} + + '@did-plc/lib@0.0.4': + resolution: {integrity: sha512-Omeawq3b8G/c/5CtkTtzovSOnWuvIuCI4GTJNrt1AmCskwEQV7zbX5d6km1mjJNbE0gHuQPTVqZxLVqetNbfwA==} + + '@did-plc/server@0.0.1': + resolution: {integrity: sha512-GtxxHcOrOQ6fNI1ufq3Zqjc2PtWqPZOdsuzlwtxiH9XibUGwDkb0GmaBHyU5GiOxOKZEW1GspZ8mreBA6XOlTQ==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@fastify/busboy@2.1.1': + resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} + engines: {node: '>=14'} + + '@growthbook/growthbook@1.6.5': + resolution: {integrity: sha512-mUaMsgeUTpRIUOTn33EUXHRK6j7pxBjwqH4WpQyq+pukjd1AIzWlEa6w7i6bInJUcweGgP2beXZmaP6b6UPn7A==} + engines: {node: '>=10'} + + '@grpc/grpc-js@1.14.4': + resolution: {integrity: sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==} + engines: {node: '>=12.10.0'} + + '@grpc/proto-loader@0.7.15': + resolution: {integrity: sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==} + engines: {node: '>=6'} + hasBin: true + + '@grpc/proto-loader@0.8.1': + resolution: {integrity: sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==} + engines: {node: '>=6'} + hasBin: true + + '@hapi/accept@6.0.3': + resolution: {integrity: sha512-p72f9k56EuF0n3MwlBNThyVE5PXX40g+aQh+C/xbKrfzahM2Oispv3AXmOIU51t3j77zay1qrX7IIziZXspMlw==} + + '@hapi/address@5.1.1': + resolution: {integrity: sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==} + engines: {node: '>=14.0.0'} + + '@hapi/boom@10.0.1': + resolution: {integrity: sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==} + + '@hapi/bourne@3.0.0': + resolution: {integrity: sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==} + + '@hapi/content@6.0.2': + resolution: {integrity: sha512-OKyCOTjNR1hftwSjk9ueyAQTw8AwapvzBrPIWMGn39vhR5PmqLdYFmLc35bsSBye7gSMnlkXfc679bUdMIcRyQ==} + + '@hapi/hoek@11.0.7': + resolution: {integrity: sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==} + + '@img/sharp-darwin-arm64@0.33.5': + resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.33.5': + resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.0.4': + resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.0.4': + resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.0.4': + resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.0.5': + resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.0.4': + resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.0.4': + resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.33.5': + resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.33.5': + resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.33.5': + resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.33.5': + resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.33.5': + resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.33.5': + resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.33.5': + resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-ia32@0.33.5': + resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.33.5': + resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@ioredis/commands@1.5.1': + resolution: {integrity: sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==} + + '@ipld/dag-cbor@7.0.3': + resolution: {integrity: sha512-1VVh2huHsuohdXC1bGJNE8WR72slZ9XE2T3wbBBq31dm7ZBatmKLLxrB+XAqafxfRFjv08RZmj/W/ZqaM13AuA==} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@js-sdsl/ordered-map@4.4.2': + resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + + '@noble/curves@1.9.7': + resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + + '@noble/secp256k1@1.7.2': + resolution: {integrity: sha512-/qzwYl5eFLH8OWIecQWM31qld2g1NfjgylK+TNhqtaUKP37Nm+Y+z30Fjhw0Ct8p9yCQEm2N3W/AckdIb3SMcQ==} + + '@nodable/entities@2.1.0': + resolution: {integrity: sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.5': + resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} + + '@protobufjs/eventemitter@1.1.1': + resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==} + + '@protobufjs/fetch@1.1.1': + resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.2': + resolution: {integrity: sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.1': + resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==} + + '@smithy/abort-controller@4.2.15': + resolution: {integrity: sha512-98zuTrdp06d5jhoIA7gBzEAh+4zZMywxdS1qiiUg0BOTq/EE4zc0bqgbSBKMcRKSNFZrpVFjspExiP7ScO2l5A==} + engines: {node: '>=18.0.0'} + + '@smithy/core@3.24.4': + resolution: {integrity: sha512-3UNRKEyQyAgVgM0LGlerCLm+ChZWZ1GPfde+jBEW6bm6bSBGU1p0EbblaUV3unbhwvidjLA5Zs3sOs7mnZwvAw==} + engines: {node: '>=18.0.0'} + + '@smithy/credential-provider-imds@4.3.4': + resolution: {integrity: sha512-vKW0MEFRU4Y3MkVZUkpJm+g9qyPGLCXhc0YLggUdSdBB4g7IaSSsCE75P9rBXyWHrXY1UYSQUl8/DwsTR7QciA==} + engines: {node: '>=18.0.0'} + + '@smithy/fetch-http-handler@5.4.4': + resolution: {integrity: sha512-qM7AUKI4G6d7lNgaZD3lA1tWSolh5r6gcixfTZAPstVURfjIbvreVTPz+994M0yC3HbX4YYhDRgr31Xy3XwWOQ==} + engines: {node: '>=18.0.0'} + + '@smithy/is-array-buffer@2.2.0': + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} + engines: {node: '>=14.0.0'} + + '@smithy/middleware-endpoint@4.5.4': + resolution: {integrity: sha512-vfaUGI2plIGPeiYlUwtC2IccLKR5XwPLCPzMwRF/dDlvMtVuy6L7Klx2LThoU3nENR294j/48Tn9alg/3teV1Q==} + engines: {node: '>=18.0.0'} + + '@smithy/node-http-handler@4.7.4': + resolution: {integrity: sha512-HIeF+1vrDGzPkkv39Hj2vlHSXHY3p958jd/8ZnePIY6+ZOsQX8coyEUKO5yQu4r0bQIVsbpotVIrXXwyycMStQ==} + engines: {node: '>=18.0.0'} + + '@smithy/signature-v4@5.4.4': + resolution: {integrity: sha512-e5UtkMvsatzBfbeBZjEOt0k0Z3BEsjTFL/n6fdO5vtBLe67tdy0dX7xw2DU7uZ3acwoHyeCqpU2Fzb7pxwHb6Q==} + engines: {node: '>=18.0.0'} + + '@smithy/smithy-client@4.13.4': + resolution: {integrity: sha512-Lg3hCVv8oVYlnQus1x+1hlNoLSrcdOhkg2+Be5YUxkI1LbCEPpcwEdYfz+0j1sQSmEixA/UUbxW41CiN/+aigA==} + engines: {node: '>=18.0.0'} + + '@smithy/types@4.14.2': + resolution: {integrity: sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-buffer-from@2.2.0': + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} + engines: {node: '>=14.0.0'} + + '@smithy/util-utf8@2.3.0': + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@tokenizer/token@0.3.0': + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + + '@ts-morph/common@0.28.1': + resolution: {integrity: sha512-W74iWf7ILp1ZKNYXY5qbddNaml7e9Sedv5lvU1V8lftlitkc9Pq1A+jlH23ltDgWYeZFFEqGCD1Ies9hqu3O+g==} + + '@types/bn.js@5.2.0': + resolution: {integrity: sha512-DLbJ1BPqxvQhIGbeu8VbUC1DiAiahHtAYvA0ZEAa4P31F7IaArc8z3C3BRQdWX4mtLQuABG4yzp76ZrS02Ui1Q==} + + '@types/elliptic@6.4.18': + resolution: {integrity: sha512-UseG6H5vjRiNpQvrhy4VF/JXdA3V/Fp5amvveaL+fs28BZ6xIKJBPnUPRlEaZpysD9MbpfaLi8lbl7PGUAkpWw==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + + '@types/node@24.12.4': + resolution: {integrity: sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==} + + '@types/node@25.9.1': + resolution: {integrity: sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==} + + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + + asn1.js@5.4.1: + resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + + await-lock@3.0.0: + resolution: {integrity: sha512-eO6fLiSnrJrMdjWMNK8zbVRXPs2TKJg78iKZd9wDpN3na5tcoV6EoeiOlMgk2QaAQ1gIrK1YuMsJHXWqz89tSA==} + + axios@1.16.1: + resolution: {integrity: sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + better-sqlite3@12.10.0: + resolution: {integrity: sha512-CyzaZRQKyHkB2ZInfTTl2nvT33EbDpjkLEbE8/Zck3Ll6O0qqvuGdrJ45HgtH+HykRg88ITY3AdreBGN70aBSQ==} + engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x || 26.x} + + big-integer@1.6.52: + resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} + engines: {node: '>=0.6'} + + bignumber.js@9.3.1: + resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + bn.js@4.12.3: + resolution: {integrity: sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==} + + body-parser@1.20.5: + resolution: {integrity: sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + bowser@2.14.1: + resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} + + brace-expansion@2.1.0: + resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} + + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} + + brorand@1.1.0: + resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} + + buffer@5.6.0: + resolution: {integrity: sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + cborg@1.10.2: + resolution: {integrity: sha512-b3tFPA9pUr2zCUiCfRd2+wok2/LBSNUMKOuRRok+WlvvAgEt/PlbgPTsZUcwCOs53IJvLgTp0eotwtosE6njug==} + hasBin: true + + cborg@4.5.8: + resolution: {integrity: sha512-6/viltD51JklRhq4L7jC3zgy6gryuG5xfZ3kzpE+PravtyeQLeQmCYLREhQH7pWENg5pY4Yu/XCd6a7dKScVlw==} + hasBin: true + + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + engines: {node: '>=0.10.0'} + + cockatiel@3.2.1: + resolution: {integrity: sha512-gfrHV6ZPkquExvMh9IOkKsBzNDk6sDuZ6DdBGUBkvFnTCqCxzpuq48RySgP0AnaqQkw2zynOFj9yly6T1Q2G5Q==} + engines: {node: '>=16'} + + code-block-writer@13.0.3: + resolution: {integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + compressible@2.0.18: + resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} + engines: {node: '>= 0.6'} + + compression@1.8.1: + resolution: {integrity: sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==} + engines: {node: '>= 0.8.0'} + + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + cookie-signature@1.0.7: + resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + core-js@3.49.0: + resolution: {integrity: sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + delay@5.0.0: + resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==} + engines: {node: '>=10'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + disposable-email-domains-js@1.25.0: + resolution: {integrity: sha512-G16ih6YqeZV9CozhVW8IASgSAhGaJ2Tr58RRtZ8Ff5glhTCEav2VX74ciETHZ01n9D57aQasEvDzaBmzATYdYw==} + + dom-mutator@0.6.0: + resolution: {integrity: sha512-iCt9o0aYfXMUkz/43ZOAUFQYotjGB+GNbYJiJdz4TgXkyToXbbRy5S6FbTp72lRBtfpUMwEc1KmpFEU4CZeoNg==} + engines: {node: '>=10'} + + dom-serializer@1.4.1: + resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@4.3.1: + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} + engines: {node: '>= 4'} + + domutils@2.8.0: + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} + + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + elliptic@6.6.1: + resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + entities@2.2.0: + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + etcd3@1.1.2: + resolution: {integrity: sha512-YIampCz1/OmrVo/tR3QltAVUtYCQQOSFoqmHKKeoHbalm+WdXe3l4rhLIylklu8EzR/I3PBiOF4dC847dDskKg==} + engines: {node: '>=16'} + + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + + express-async-errors@3.1.1: + resolution: {integrity: sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng==} + peerDependencies: + express: ^4.16.2 + + express@4.22.2: + resolution: {integrity: sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==} + engines: {node: '>= 0.10.0'} + + fast-printf@1.6.10: + resolution: {integrity: sha512-GwTgG9O4FVIdShhbVF3JxOgSBY2+ePGsu2V/UONgoCPzF9VY6ZdBMKsHKCYQHZwNk3qNouUolRDsgVxcVA5G1w==} + engines: {node: '>=10.0'} + + fast-redact@3.5.0: + resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} + engines: {node: '>=6'} + + fast-xml-builder@1.2.0: + resolution: {integrity: sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==} + + fast-xml-parser@5.7.3: + resolution: {integrity: sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==} + hasBin: true + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-type@16.5.4: + resolution: {integrity: sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==} + engines: {node: '>=10'} + + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + + finalhandler@1.3.2: + resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} + engines: {node: '>= 0.8'} + + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + french-badwords-list@1.0.7: + resolution: {integrity: sha512-H1ziKs2PJh2+UXZ9oCGJ/rRQpsI9NBykGf2Sc7WaKaj1OnWFuBXfsvANTdRcfVmOghGQaUmRyZ1hJOPbDpy04Q==} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-port@5.1.1: + resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} + engines: {node: '>=8'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + handlebars@4.7.9: + resolution: {integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==} + engines: {node: '>=0.4.7'} + hasBin: true + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hash.js@1.1.7: + resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} + + hasown@2.0.3: + resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} + engines: {node: '>= 0.4'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + hmac-drbg@1.0.1: + resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} + + html-to-text@7.1.1: + resolution: {integrity: sha512-c9QWysrfnRZevVpS8MlE7PyOdSuIOjg8Bt8ZE10jMU/BEngA6j3llj4GRfAmtQzcd1FjKE0sWu5IHXRUH9YxIQ==} + engines: {node: '>=10.23.2'} + hasBin: true + + htmlparser2@6.1.0: + resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + http-terminator@3.2.0: + resolution: {integrity: sha512-JLjck1EzPaWjsmIf8bziM3p9fgR1Y3JoUKAkyYEbZmFrIvJM6I8vVJfBGWlEtV9IWOvzNnaTtjuwZeBY2kwB4g==} + engines: {node: '>=14'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + ioredis@5.10.1: + resolution: {integrity: sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==} + engines: {node: '>=12.22.0'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + ipaddr.js@2.4.0: + resolution: {integrity: sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==} + engines: {node: '>= 10'} + + is-arrayish@0.3.4: + resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + iso-datestring-validator@2.2.2: + resolution: {integrity: sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jose@5.10.0: + resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==} + + key-encoder@2.0.3: + resolution: {integrity: sha512-fgBtpAGIr/Fy5/+ZLQZIPPhsZEcbSlYu/Wu96tNDFNSjSACw5lEIOFeaVdQ/iwrb8oxjlWi6wmWdH76hV6GZjg==} + + kysely@0.22.0: + resolution: {integrity: sha512-ZE3qWtnqLOalodzfK5QUEcm7AEulhxsPNuKaGFsC3XiqO92vMLm+mAHk/NnbSIOtC4RmGm0nsv700i8KDp1gfQ==} + engines: {node: '>=14.0.0'} + + kysely@0.23.5: + resolution: {integrity: sha512-TH+b56pVXQq0tsyooYLeNfV11j6ih7D50dyN8tkM0e7ndiUH28Nziojiog3qRFlmEj9XePYdZUrNJ2079Qjdow==} + engines: {node: '>=14.0.0'} + + lande@1.0.10: + resolution: {integrity: sha512-yT52DQh+UV2pEp08jOYrA4drDv0DbjpiRyZYgl25ak9G2cVR2AimzrqkYQWrD9a7Ud+qkAcaiDDoNH9DXfHPmw==} + + leo-profanity@1.9.0: + resolution: {integrity: sha512-vMrrrjsbT+fA5I/1rlBEVT5YjJsw1ASIVF8/xBEdZ6ylsg5AEIBrEvHxwe5XAmfVuTBi7aw2KcO2L3s9ddpKzw==} + engines: {node: '>=18'} + + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + + lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + + lodash.isarguments@3.1.0: + resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + minimalistic-assert@1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + + minimalistic-crypto-utils@1.0.1: + resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + multiformats@13.4.2: + resolution: {integrity: sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==} + + multiformats@9.9.0: + resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==} + + murmurhash@2.0.1: + resolution: {integrity: sha512-5vQEh3y+DG/lMPM0mCGPDnyV8chYg/g7rl6v3Gd8WMF9S429ox3Xk8qrk174kWhG767KQMqqxLD1WnGd77hiew==} + + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + negotiator@0.6.4: + resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} + engines: {node: '>= 0.6'} + + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + node-abi@3.92.0: + resolution: {integrity: sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==} + engines: {node: '>=10'} + + nodemailer-html-to-text@3.2.0: + resolution: {integrity: sha512-RJUC6640QV1PzTHHapOrc6IzrAJUZtk2BdVdINZ9VTLm+mcQNyBO9LYyhrnufkzqiD9l8hPLJ97rSyK4WanPNg==} + engines: {node: '>= 10.23.0'} + + nodemailer@6.10.1: + resolution: {integrity: sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==} + engines: {node: '>=6.0.0'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + on-headers@1.1.0: + resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + one-webcrypto@1.0.3: + resolution: {integrity: sha512-fu9ywBVBPx0gS9K0etIROTiCkvI5S1TDjFsYFb3rC1ewFxeOqsbzq7aIMBHsYfrTHBcGXJaONXXjTl8B01cW1Q==} + + p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} + + p-queue@8.1.1: + resolution: {integrity: sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==} + engines: {node: '>=18'} + + p-timeout@3.2.0: + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} + engines: {node: '>=8'} + + p-timeout@6.1.4: + resolution: {integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==} + engines: {node: '>=14.16'} + + p-wait-for@3.2.0: + resolution: {integrity: sha512-wpgERjNkLrBiFmkMEjuZJEWKKDrNfHCKA1OhyN1wg1FrLkULbviEy6py1AyJUgZ72YWFbZ38FIpnqvVqAlDUwA==} + engines: {node: '>=8'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-expression-matcher@1.5.0: + resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==} + engines: {node: '>=14.0.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-to-regexp@0.1.13: + resolution: {integrity: sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==} + + peek-readable@4.1.0: + resolution: {integrity: sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==} + engines: {node: '>=8'} + + pg-cloudflare@1.4.0: + resolution: {integrity: sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==} + + pg-connection-string@2.13.0: + resolution: {integrity: sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig==} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-pool@3.14.0: + resolution: {integrity: sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==} + peerDependencies: + pg: '>=8.0' + + pg-protocol@1.14.0: + resolution: {integrity: sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + + pg@8.21.0: + resolution: {integrity: sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==} + engines: {node: '>= 16.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + + pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pino-abstract-transport@1.2.0: + resolution: {integrity: sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==} + + pino-http@8.6.1: + resolution: {integrity: sha512-J0hiJgUExtBXP2BjrK4VB305tHXS31sCmWJ9XJo2wPkLHa1NFPuW4V9wjG27PAc2fmBCigiNhQKpvrx+kntBPA==} + + pino-std-serializers@6.2.2: + resolution: {integrity: sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==} + + pino@8.21.0: + resolution: {integrity: sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==} + hasBin: true + + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-bytea@1.0.1: + resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} + engines: {node: '>=0.10.0'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available. + hasBin: true + + prettier@3.8.3: + resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} + engines: {node: '>=14'} + hasBin: true + + process-warning@3.0.0: + resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==} + + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + + protobufjs@7.6.1: + resolution: {integrity: sha512-4K0myLaWL5EteuSAro91EGFgcfVgxb64Jx+7oDAY6GOkXD4M69yuSEljNcInGVCA5sOPxmZ/EqDLj2x0Q0+Ygg==} + engines: {node: '>=12.0.0'} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} + + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + + qs@6.15.1: + resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} + engines: {node: '>=0.6'} + + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + rate-limiter-flexible@2.4.2: + resolution: {integrity: sha512-rMATGGOdO1suFyf/mI5LYhts71g1sbdhmd6YvdiXO2gJnd42Tt6QS4JUKJKSWVVkMtBacm6l40FR7Trjo6Iruw==} + + raw-body@2.5.3: + resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} + engines: {node: '>= 0.8'} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readable-stream@4.7.0: + resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + readable-web-to-node-stream@3.0.4: + resolution: {integrity: sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==} + engines: {node: '>=8'} + + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + + redis-errors@1.2.0: + resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} + engines: {node: '>=4'} + + redis-parser@3.0.0: + resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} + engines: {node: '>=4'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + roarr@7.21.5: + resolution: {integrity: sha512-nvelZ4llbfodVanR/gG17H8jpnqgyPX01c4ekQYfoghjEKvAXn7aPPToVG8ngyxf4qtvTC1O5AxQe5PysnF4xg==} + engines: {node: '>=18.0'} + + russian-bad-words@0.5.0: + resolution: {integrity: sha512-euNvEYki6iYYpkNbeudW+lEMMYGEmN7EBwVF8ezlbv0bZoQpVYB7W10cCeUIGV7Ed50sJynLQ0c559q5iI0ejQ==} + engines: {node: '>=10'} + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + semver-compare@1.0.0: + resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} + + semver@7.8.0: + resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} + engines: {node: '>=10'} + hasBin: true + + send@0.19.2: + resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} + engines: {node: '>= 0.8.0'} + + serve-static@1.16.3: + resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} + engines: {node: '>= 0.8.0'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + sharp@0.33.5: + resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + + simple-swizzle@0.2.4: + resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} + + sonic-boom@3.8.1: + resolution: {integrity: sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + standard-as-callback@2.1.0: + resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + stream-browserify@3.0.0: + resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + strnum@2.3.0: + resolution: {integrity: sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==} + + strtok3@6.3.0: + resolution: {integrity: sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==} + engines: {node: '>=10'} + + structured-headers@1.0.1: + resolution: {integrity: sha512-QYBxdBtA4Tl5rFPuqmbmdrS9kbtren74RTJTcs0VSQNVV5iRhJD4QlYTLD0+81SBwUQctjEQzjTRI3WG4DzICA==} + engines: {node: '>= 14', npm: '>=6'} + + tar-fs@2.1.4: + resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + thread-stream@2.7.0: + resolution: {integrity: sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==} + + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + + tlds@1.261.0: + resolution: {integrity: sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA==} + hasBin: true + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + token-types@4.2.1: + resolution: {integrity: sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==} + engines: {node: '>=10'} + + toygrad@2.6.0: + resolution: {integrity: sha512-g4zBmlSbvzOE5FOILxYkAybTSxijKLkj1WoNqVGnbMcWDyj4wWQ+eYSr3ik7XOpIgMq/7eBcPRTJX3DM2E0YMg==} + + ts-morph@27.0.2: + resolution: {integrity: sha512-fhUhgeljcrdZ+9DZND1De1029PrE+cMkIP7ooqkLRTrRLTqcki2AstsyJm0vRNbTbVCNJ0idGlbBrfqc7/nA8w==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + typed-emitter@2.1.0: + resolution: {integrity: sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==} + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + + uint8arrays@3.0.0: + resolution: {integrity: sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==} + + uint8arrays@5.1.1: + resolution: {integrity: sha512-9muQwa4wZG4dKi9gMAIBtnk2Pw87SRpvWTH6lOGm19V2Uqxr4uomUf2PGqPnWc+qs06sN8owUU4jfcoWOcfwVQ==} + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + + undici@5.29.0: + resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==} + engines: {node: '>=14.0'} + + undici@6.25.0: + resolution: {integrity: sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==} + engines: {node: '>=18.17'} + + unicode-segmenter@0.14.5: + resolution: {integrity: sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g==} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + varint@6.0.0: + resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.20.1: + resolution: {integrity: sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-naming@0.1.0: + resolution: {integrity: sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==} + engines: {node: '>=16.0.0'} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + zod@3.23.8: + resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + +snapshots: + + '@atproto-labs/did-resolver@0.3.0': + dependencies: + '@atproto-labs/fetch': 0.3.0 + '@atproto-labs/pipe': 0.2.0 + '@atproto-labs/simple-store': 0.4.0 + '@atproto-labs/simple-store-memory': 0.2.0 + '@atproto/did': 0.4.0 + zod: 3.25.76 + + '@atproto-labs/fetch-node@0.3.0': + dependencies: + '@atproto-labs/fetch': 0.3.0 + '@atproto-labs/pipe': 0.2.0 + ipaddr.js: 2.4.0 + undici: 6.25.0 + + '@atproto-labs/fetch@0.3.0': + dependencies: + '@atproto-labs/pipe': 0.2.0 + + '@atproto-labs/pipe@0.2.0': {} + + '@atproto-labs/simple-store-memory@0.2.0': + dependencies: + '@atproto-labs/simple-store': 0.4.0 + lru-cache: 10.4.3 + + '@atproto-labs/simple-store-redis@0.1.0(ioredis@5.10.1)': + dependencies: + '@atproto-labs/simple-store': 0.4.0 + ioredis: 5.10.1 + + '@atproto-labs/simple-store@0.4.0': {} + + '@atproto-labs/xrpc-utils@0.1.0': + dependencies: + '@atproto/xrpc': 0.8.0 + '@atproto/xrpc-server': 0.11.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@atproto/api@0.20.5': + dependencies: + '@atproto/common-web': 0.5.0 + '@atproto/lexicon': 0.7.1 + '@atproto/syntax': 0.6.1 + '@atproto/xrpc': 0.8.0 + await-lock: 3.0.0 + multiformats: 13.4.2 + tlds: 1.261.0 + zod: 3.25.76 + + '@atproto/aws@0.3.0': + dependencies: + '@atproto/common': 0.6.1 + '@atproto/common-web': 0.5.0 + '@atproto/crypto': 0.5.0 + '@atproto/repo': 0.10.0 + '@aws-sdk/client-cloudfront': 3.1053.0 + '@aws-sdk/client-kms': 3.1053.0 + '@aws-sdk/client-s3': 3.1053.0 + '@aws-sdk/lib-storage': 3.879.0(@aws-sdk/client-s3@3.1053.0) + '@noble/curves': 1.9.7 + key-encoder: 2.0.3 + multiformats: 13.4.2 + uint8arrays: 5.1.1 + + '@atproto/bsky@0.0.234': + dependencies: + '@atproto-labs/fetch-node': 0.3.0 + '@atproto-labs/xrpc-utils': 0.1.0 + '@atproto/api': 0.20.5 + '@atproto/common': 0.6.1 + '@atproto/crypto': 0.5.0 + '@atproto/did': 0.4.0 + '@atproto/identity': 0.5.0 + '@atproto/lex': 0.1.2 + '@atproto/repo': 0.10.0 + '@atproto/sync': 0.3.1 + '@atproto/syntax': 0.6.1 + '@atproto/xrpc-server': 0.11.1 + '@bufbuild/protobuf': 1.10.1 + '@connectrpc/connect': 1.7.0(@bufbuild/protobuf@1.10.1) + '@connectrpc/connect-express': 1.7.0(@bufbuild/protobuf@1.10.1)(@connectrpc/connect-node@1.7.0(@bufbuild/protobuf@1.10.1)(@connectrpc/connect@1.7.0(@bufbuild/protobuf@1.10.1)))(@connectrpc/connect@1.7.0(@bufbuild/protobuf@1.10.1)) + '@connectrpc/connect-node': 1.7.0(@bufbuild/protobuf@1.10.1)(@connectrpc/connect@1.7.0(@bufbuild/protobuf@1.10.1)) + '@did-plc/lib': 0.0.1 + '@growthbook/growthbook': 1.6.5 + '@hapi/address': 5.1.1 + '@types/http-errors': 2.0.5 + compression: 1.8.1 + cors: 2.8.6 + disposable-email-domains-js: 1.25.0 + etcd3: 1.1.2 + express: 4.22.2 + http-errors: 2.0.1 + http-terminator: 3.2.0 + ioredis: 5.10.1 + jose: 5.10.0 + key-encoder: 2.0.3 + kysely: 0.22.0 + leo-profanity: 1.9.0 + murmurhash: 2.0.1 + p-queue: 8.1.1 + pg: 8.21.0 + pino: 8.21.0 + pino-http: 8.6.1 + sharp: 0.33.5 + structured-headers: 1.0.1 + typed-emitter: 2.1.0 + uint8arrays: 5.1.1 + undici: 6.25.0 + zod: 3.23.8 + transitivePeerDependencies: + - bufferutil + - debug + - pg-native + - supports-color + - utf-8-validate + + '@atproto/bsync@0.0.27': + dependencies: + '@atproto/common': 0.6.1 + '@atproto/syntax': 0.6.1 + '@bufbuild/protobuf': 1.10.1 + '@connectrpc/connect': 1.7.0(@bufbuild/protobuf@1.10.1) + '@connectrpc/connect-node': 1.7.0(@bufbuild/protobuf@1.10.1)(@connectrpc/connect@1.7.0(@bufbuild/protobuf@1.10.1)) + http-terminator: 3.2.0 + kysely: 0.22.0 + pg: 8.21.0 + pino-http: 8.6.1 + typed-emitter: 2.1.0 + transitivePeerDependencies: + - pg-native + + '@atproto/common-web@0.5.0': + dependencies: + '@atproto/lex-data': 0.1.1 + '@atproto/lex-json': 0.1.0 + '@atproto/syntax': 0.6.1 + zod: 3.25.76 + + '@atproto/common@0.1.0': + dependencies: + '@ipld/dag-cbor': 7.0.3 + multiformats: 9.9.0 + pino: 8.21.0 + zod: 3.25.76 + + '@atproto/common@0.1.1': + dependencies: + '@ipld/dag-cbor': 7.0.3 + multiformats: 9.9.0 + pino: 8.21.0 + zod: 3.25.76 + + '@atproto/common@0.6.1': + dependencies: + '@atproto/common-web': 0.5.0 + '@atproto/lex-cbor': 0.1.0 + '@atproto/lex-data': 0.1.1 + multiformats: 13.4.2 + pino: 8.21.0 + + '@atproto/crypto@0.1.0': + dependencies: + '@noble/secp256k1': 1.7.2 + big-integer: 1.6.52 + multiformats: 9.9.0 + one-webcrypto: 1.0.3 + uint8arrays: 3.0.0 + + '@atproto/crypto@0.5.0': + dependencies: + '@noble/curves': 1.9.7 + '@noble/hashes': 1.8.0 + uint8arrays: 5.1.1 + + '@atproto/dev-env@0.5.3': + dependencies: + '@atproto/api': 0.20.5 + '@atproto/bsky': 0.0.234 + '@atproto/bsync': 0.0.27 + '@atproto/common-web': 0.5.0 + '@atproto/crypto': 0.5.0 + '@atproto/identity': 0.5.0 + '@atproto/lex': 0.1.2 + '@atproto/lexicon': 0.7.1 + '@atproto/ozone': 0.1.176 + '@atproto/pds': 0.5.0 + '@atproto/sync': 0.3.1 + '@atproto/syntax': 0.6.1 + '@atproto/xrpc-server': 0.11.1 + '@did-plc/lib': 0.0.1 + '@did-plc/server': 0.0.1 + dotenv: 16.6.1 + express: 4.22.2 + get-port: 5.1.1 + multiformats: 13.4.2 + uint8arrays: 5.1.1 + undici: 6.25.0 + transitivePeerDependencies: + - bufferutil + - debug + - pg-native + - supports-color + - utf-8-validate + + '@atproto/did@0.4.0': + dependencies: + zod: 3.25.76 + + '@atproto/identity@0.5.0': + dependencies: + '@atproto/common-web': 0.5.0 + '@atproto/crypto': 0.5.0 + + '@atproto/jwk-jose@0.2.0': + dependencies: + '@atproto/jwk': 0.7.0 + jose: 5.10.0 + + '@atproto/jwk@0.7.0': + dependencies: + multiformats: 13.4.2 + zod: 3.25.76 + + '@atproto/lex-builder@0.1.2': + dependencies: + '@atproto/lex-document': 0.1.0 + '@atproto/lex-schema': 0.1.1 + prettier: 3.8.3 + ts-morph: 27.0.2 + tslib: 2.8.1 + + '@atproto/lex-cbor@0.1.0': + dependencies: + '@atproto/lex-data': 0.1.1 + cborg: 4.5.8 + tslib: 2.8.1 + + '@atproto/lex-client@0.1.2': + dependencies: + '@atproto/lex-data': 0.1.1 + '@atproto/lex-json': 0.1.0 + '@atproto/lex-schema': 0.1.1 + tslib: 2.8.1 + + '@atproto/lex-data@0.1.1': + dependencies: + multiformats: 13.4.2 + tslib: 2.8.1 + uint8arrays: 5.1.1 + unicode-segmenter: 0.14.5 + + '@atproto/lex-document@0.1.0': + dependencies: + '@atproto/lex-schema': 0.1.1 + core-js: 3.49.0 + tslib: 2.8.1 + + '@atproto/lex-installer@0.1.0': + dependencies: + '@atproto/lex-builder': 0.1.2 + '@atproto/lex-cbor': 0.1.0 + '@atproto/lex-data': 0.1.1 + '@atproto/lex-document': 0.1.0 + '@atproto/lex-resolver': 0.1.0 + '@atproto/lex-schema': 0.1.1 + '@atproto/syntax': 0.6.1 + tslib: 2.8.1 + + '@atproto/lex-json@0.1.0': + dependencies: + '@atproto/lex-data': 0.1.1 + tslib: 2.8.1 + + '@atproto/lex-resolver@0.1.0': + dependencies: + '@atproto-labs/did-resolver': 0.3.0 + '@atproto/crypto': 0.5.0 + '@atproto/lex-client': 0.1.2 + '@atproto/lex-data': 0.1.1 + '@atproto/lex-document': 0.1.0 + '@atproto/lex-schema': 0.1.1 + '@atproto/repo': 0.10.0 + '@atproto/syntax': 0.6.1 + tslib: 2.8.1 + + '@atproto/lex-schema@0.1.1': + dependencies: + '@atproto/lex-data': 0.1.1 + '@atproto/syntax': 0.6.1 + '@standard-schema/spec': 1.1.0 + tslib: 2.8.1 + + '@atproto/lex@0.1.2': + dependencies: + '@atproto/lex-builder': 0.1.2 + '@atproto/lex-client': 0.1.2 + '@atproto/lex-data': 0.1.1 + '@atproto/lex-installer': 0.1.0 + '@atproto/lex-json': 0.1.0 + '@atproto/lex-schema': 0.1.1 + tslib: 2.8.1 + yargs: 17.7.2 + + '@atproto/lexicon@0.7.1': + dependencies: + '@atproto/common-web': 0.5.0 + '@atproto/syntax': 0.6.1 + multiformats: 13.4.2 + zod: 3.25.76 + + '@atproto/oauth-provider-api@0.6.0': + dependencies: + '@atproto/jwk': 0.7.0 + '@atproto/oauth-types': 0.7.0 + + '@atproto/oauth-provider-ui@0.7.0': + optionalDependencies: + '@atproto/oauth-provider-api': 0.6.0 + + '@atproto/oauth-provider@0.18.0': + dependencies: + '@atproto-labs/fetch': 0.3.0 + '@atproto-labs/fetch-node': 0.3.0 + '@atproto-labs/pipe': 0.2.0 + '@atproto-labs/simple-store': 0.4.0 + '@atproto-labs/simple-store-memory': 0.2.0 + '@atproto/common': 0.6.1 + '@atproto/did': 0.4.0 + '@atproto/jwk': 0.7.0 + '@atproto/jwk-jose': 0.2.0 + '@atproto/lex-document': 0.1.0 + '@atproto/lex-resolver': 0.1.0 + '@atproto/oauth-provider-api': 0.6.0 + '@atproto/oauth-provider-ui': 0.7.0 + '@atproto/oauth-scopes': 0.4.0 + '@atproto/oauth-types': 0.7.0 + '@atproto/syntax': 0.6.1 + '@hapi/accept': 6.0.3 + '@hapi/address': 5.1.1 + '@hapi/bourne': 3.0.0 + '@hapi/content': 6.0.2 + cookie: 0.7.2 + disposable-email-domains-js: 1.25.0 + forwarded: 0.2.0 + http-errors: 2.0.1 + ioredis: 5.10.1 + jose: 5.10.0 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + + '@atproto/oauth-scopes@0.4.0': + dependencies: + '@atproto/did': 0.4.0 + '@atproto/syntax': 0.6.1 + + '@atproto/oauth-types@0.7.0': + dependencies: + '@atproto/did': 0.4.0 + '@atproto/jwk': 0.7.0 + zod: 3.25.76 + + '@atproto/ozone@0.1.176': + dependencies: + '@atproto/api': 0.20.5 + '@atproto/common': 0.6.1 + '@atproto/crypto': 0.5.0 + '@atproto/identity': 0.5.0 + '@atproto/lexicon': 0.7.1 + '@atproto/syntax': 0.6.1 + '@atproto/ws-client': 0.1.0 + '@atproto/xrpc': 0.8.0 + '@atproto/xrpc-server': 0.11.1 + '@did-plc/lib': 0.0.1 + compression: 1.8.1 + cors: 2.8.6 + express: 4.22.2 + http-terminator: 3.2.0 + kysely: 0.22.0 + lande: 1.0.10 + multiformats: 13.4.2 + p-queue: 8.1.1 + pg: 8.21.0 + pino-http: 8.6.1 + structured-headers: 1.0.1 + typed-emitter: 2.1.0 + uint8arrays: 5.1.1 + undici: 6.25.0 + ws: 8.20.1 + transitivePeerDependencies: + - bufferutil + - debug + - pg-native + - supports-color + - utf-8-validate + + '@atproto/pds@0.5.0': + dependencies: + '@atproto-labs/fetch-node': 0.3.0 + '@atproto-labs/simple-store': 0.4.0 + '@atproto-labs/simple-store-memory': 0.2.0 + '@atproto-labs/simple-store-redis': 0.1.0(ioredis@5.10.1) + '@atproto-labs/xrpc-utils': 0.1.0 + '@atproto/aws': 0.3.0 + '@atproto/common': 0.6.1 + '@atproto/crypto': 0.5.0 + '@atproto/identity': 0.5.0 + '@atproto/lex': 0.1.2 + '@atproto/lex-cbor': 0.1.0 + '@atproto/lex-data': 0.1.1 + '@atproto/lex-json': 0.1.0 + '@atproto/oauth-provider': 0.18.0 + '@atproto/oauth-scopes': 0.4.0 + '@atproto/repo': 0.10.0 + '@atproto/syntax': 0.6.1 + '@atproto/xrpc': 0.8.0 + '@atproto/xrpc-server': 0.11.1 + '@did-plc/lib': 0.0.4 + '@hapi/address': 5.1.1 + better-sqlite3: 12.10.0 + compression: 1.8.1 + cors: 2.8.6 + disposable-email-domains-js: 1.25.0 + express: 4.22.2 + express-async-errors: 3.1.1(express@4.22.2) + file-type: 16.5.4 + glob: 10.5.0 + handlebars: 4.7.9 + http-terminator: 3.2.0 + ioredis: 5.10.1 + jose: 5.10.0 + key-encoder: 2.0.3 + kysely: 0.22.0 + nodemailer: 6.10.1 + nodemailer-html-to-text: 3.2.0 + p-queue: 8.1.1 + pino: 8.21.0 + pino-http: 8.6.1 + typed-emitter: 2.1.0 + uint8arrays: 5.1.1 + undici: 6.25.0 + zod: 3.25.76 + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + + '@atproto/repo@0.10.0': + dependencies: + '@atproto/common': 0.6.1 + '@atproto/common-web': 0.5.0 + '@atproto/crypto': 0.5.0 + '@atproto/lex-cbor': 0.1.0 + '@atproto/lex-data': 0.1.1 + '@atproto/syntax': 0.6.1 + varint: 6.0.0 + zod: 3.25.76 + + '@atproto/sync@0.3.1': + dependencies: + '@atproto/common': 0.6.1 + '@atproto/identity': 0.5.0 + '@atproto/lex': 0.1.2 + '@atproto/repo': 0.10.0 + '@atproto/syntax': 0.6.1 + '@atproto/xrpc-server': 0.11.1 + p-queue: 8.1.1 + ws: 8.20.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@atproto/syntax@0.6.1': + dependencies: + iso-datestring-validator: 2.2.2 + tslib: 2.8.1 + + '@atproto/ws-client@0.1.0': + dependencies: + '@atproto/common': 0.6.1 + ws: 8.20.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@atproto/xrpc-server@0.11.1': + dependencies: + '@atproto/common': 0.6.1 + '@atproto/crypto': 0.5.0 + '@atproto/lex-cbor': 0.1.0 + '@atproto/lex-client': 0.1.2 + '@atproto/lex-data': 0.1.1 + '@atproto/lex-json': 0.1.0 + '@atproto/lex-schema': 0.1.1 + '@atproto/lexicon': 0.7.1 + '@atproto/ws-client': 0.1.0 + '@atproto/xrpc': 0.8.0 + express: 4.22.2 + http-errors: 2.0.1 + mime-types: 2.1.35 + rate-limiter-flexible: 2.4.2 + ws: 8.20.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@atproto/xrpc@0.8.0': + dependencies: + '@atproto/lexicon': 0.7.1 + zod: 3.25.76 + + '@aws-crypto/crc32@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.9 + tslib: 2.8.1 + + '@aws-crypto/crc32c@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.9 + tslib: 2.8.1 + + '@aws-crypto/sha1-browser@5.2.0': + dependencies: + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.9 + '@aws-sdk/util-locate-window': 3.965.5 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-browser@5.2.0': + dependencies: + '@aws-crypto/sha256-js': 5.2.0 + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.9 + '@aws-sdk/util-locate-window': 3.965.5 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-js@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.9 + tslib: 2.8.1 + + '@aws-crypto/supports-web-crypto@5.2.0': + dependencies: + tslib: 2.8.1 + + '@aws-crypto/util@5.2.0': + dependencies: + '@aws-sdk/types': 3.973.9 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-sdk/client-cloudfront@3.1053.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.974.13 + '@aws-sdk/credential-provider-node': 3.972.44 + '@aws-sdk/types': 3.973.9 + '@smithy/core': 3.24.4 + '@smithy/fetch-http-handler': 5.4.4 + '@smithy/node-http-handler': 4.7.4 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/client-kms@3.1053.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.974.13 + '@aws-sdk/credential-provider-node': 3.972.44 + '@aws-sdk/types': 3.973.9 + '@smithy/core': 3.24.4 + '@smithy/fetch-http-handler': 5.4.4 + '@smithy/node-http-handler': 4.7.4 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/client-s3@3.1053.0': + dependencies: + '@aws-crypto/sha1-browser': 5.2.0 + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.974.13 + '@aws-sdk/credential-provider-node': 3.972.44 + '@aws-sdk/middleware-bucket-endpoint': 3.972.15 + '@aws-sdk/middleware-expect-continue': 3.972.13 + '@aws-sdk/middleware-flexible-checksums': 3.974.21 + '@aws-sdk/middleware-location-constraint': 3.972.11 + '@aws-sdk/middleware-sdk-s3': 3.972.42 + '@aws-sdk/middleware-ssec': 3.972.11 + '@aws-sdk/signature-v4-multi-region': 3.996.28 + '@aws-sdk/types': 3.973.9 + '@smithy/core': 3.24.4 + '@smithy/fetch-http-handler': 5.4.4 + '@smithy/node-http-handler': 4.7.4 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/core@3.974.13': + dependencies: + '@aws-sdk/types': 3.973.9 + '@aws-sdk/xml-builder': 3.972.25 + '@aws/lambda-invoke-store': 0.2.4 + '@smithy/core': 3.24.4 + '@smithy/signature-v4': 5.4.4 + '@smithy/types': 4.14.2 + bowser: 2.14.1 + tslib: 2.8.1 + + '@aws-sdk/crc64-nvme@3.972.9': + dependencies: + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.972.39': + dependencies: + '@aws-sdk/core': 3.974.13 + '@aws-sdk/types': 3.973.9 + '@smithy/core': 3.24.4 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.972.41': + dependencies: + '@aws-sdk/core': 3.974.13 + '@aws-sdk/types': 3.973.9 + '@smithy/core': 3.24.4 + '@smithy/fetch-http-handler': 5.4.4 + '@smithy/node-http-handler': 4.7.4 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.972.43': + dependencies: + '@aws-sdk/core': 3.974.13 + '@aws-sdk/credential-provider-env': 3.972.39 + '@aws-sdk/credential-provider-http': 3.972.41 + '@aws-sdk/credential-provider-login': 3.972.43 + '@aws-sdk/credential-provider-process': 3.972.39 + '@aws-sdk/credential-provider-sso': 3.972.43 + '@aws-sdk/credential-provider-web-identity': 3.972.43 + '@aws-sdk/nested-clients': 3.997.11 + '@aws-sdk/types': 3.973.9 + '@smithy/core': 3.24.4 + '@smithy/credential-provider-imds': 4.3.4 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-login@3.972.43': + dependencies: + '@aws-sdk/core': 3.974.13 + '@aws-sdk/nested-clients': 3.997.11 + '@aws-sdk/types': 3.973.9 + '@smithy/core': 3.24.4 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-node@3.972.44': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.39 + '@aws-sdk/credential-provider-http': 3.972.41 + '@aws-sdk/credential-provider-ini': 3.972.43 + '@aws-sdk/credential-provider-process': 3.972.39 + '@aws-sdk/credential-provider-sso': 3.972.43 + '@aws-sdk/credential-provider-web-identity': 3.972.43 + '@aws-sdk/types': 3.973.9 + '@smithy/core': 3.24.4 + '@smithy/credential-provider-imds': 4.3.4 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-process@3.972.39': + dependencies: + '@aws-sdk/core': 3.974.13 + '@aws-sdk/types': 3.973.9 + '@smithy/core': 3.24.4 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.972.43': + dependencies: + '@aws-sdk/core': 3.974.13 + '@aws-sdk/nested-clients': 3.997.11 + '@aws-sdk/token-providers': 3.1052.0 + '@aws-sdk/types': 3.973.9 + '@smithy/core': 3.24.4 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-web-identity@3.972.43': + dependencies: + '@aws-sdk/core': 3.974.13 + '@aws-sdk/nested-clients': 3.997.11 + '@aws-sdk/types': 3.973.9 + '@smithy/core': 3.24.4 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/lib-storage@3.879.0(@aws-sdk/client-s3@3.1053.0)': + dependencies: + '@aws-sdk/client-s3': 3.1053.0 + '@smithy/abort-controller': 4.2.15 + '@smithy/middleware-endpoint': 4.5.4 + '@smithy/smithy-client': 4.13.4 + buffer: 5.6.0 + events: 3.3.0 + stream-browserify: 3.0.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-bucket-endpoint@3.972.15': + dependencies: + '@aws-sdk/core': 3.974.13 + '@aws-sdk/types': 3.973.9 + '@smithy/core': 3.24.4 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-expect-continue@3.972.13': + dependencies: + '@aws-sdk/types': 3.973.9 + '@smithy/core': 3.24.4 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-flexible-checksums@3.974.21': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@aws-crypto/crc32c': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/core': 3.974.13 + '@aws-sdk/crc64-nvme': 3.972.9 + '@aws-sdk/types': 3.973.9 + '@smithy/core': 3.24.4 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-location-constraint@3.972.11': + dependencies: + '@aws-sdk/types': 3.973.9 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-s3@3.972.42': + dependencies: + '@aws-sdk/core': 3.974.13 + '@aws-sdk/signature-v4-multi-region': 3.996.28 + '@aws-sdk/types': 3.973.9 + '@smithy/core': 3.24.4 + '@smithy/signature-v4': 5.4.4 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-ssec@3.972.11': + dependencies: + '@aws-sdk/types': 3.973.9 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/nested-clients@3.997.11': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.974.13 + '@aws-sdk/signature-v4-multi-region': 3.996.28 + '@aws-sdk/types': 3.973.9 + '@smithy/core': 3.24.4 + '@smithy/fetch-http-handler': 5.4.4 + '@smithy/node-http-handler': 4.7.4 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/signature-v4-multi-region@3.996.28': + dependencies: + '@aws-sdk/types': 3.973.9 + '@smithy/core': 3.24.4 + '@smithy/signature-v4': 5.4.4 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.1052.0': + dependencies: + '@aws-sdk/core': 3.974.13 + '@aws-sdk/nested-clients': 3.997.11 + '@aws-sdk/types': 3.973.9 + '@smithy/core': 3.24.4 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/types@3.973.9': + dependencies: + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/util-locate-window@3.965.5': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.972.25': + dependencies: + '@nodable/entities': 2.1.0 + '@smithy/types': 4.14.2 + fast-xml-parser: 5.7.3 + tslib: 2.8.1 + + '@aws/lambda-invoke-store@0.2.4': {} + + '@bufbuild/protobuf@1.10.1': {} + + '@connectrpc/connect-express@1.7.0(@bufbuild/protobuf@1.10.1)(@connectrpc/connect-node@1.7.0(@bufbuild/protobuf@1.10.1)(@connectrpc/connect@1.7.0(@bufbuild/protobuf@1.10.1)))(@connectrpc/connect@1.7.0(@bufbuild/protobuf@1.10.1))': + dependencies: + '@bufbuild/protobuf': 1.10.1 + '@connectrpc/connect': 1.7.0(@bufbuild/protobuf@1.10.1) + '@connectrpc/connect-node': 1.7.0(@bufbuild/protobuf@1.10.1)(@connectrpc/connect@1.7.0(@bufbuild/protobuf@1.10.1)) + + '@connectrpc/connect-node@1.7.0(@bufbuild/protobuf@1.10.1)(@connectrpc/connect@1.7.0(@bufbuild/protobuf@1.10.1))': + dependencies: + '@bufbuild/protobuf': 1.10.1 + '@connectrpc/connect': 1.7.0(@bufbuild/protobuf@1.10.1) + undici: 5.29.0 + + '@connectrpc/connect@1.7.0(@bufbuild/protobuf@1.10.1)': + dependencies: + '@bufbuild/protobuf': 1.10.1 + + '@did-plc/lib@0.0.1': + dependencies: + '@atproto/common': 0.1.0 + '@atproto/crypto': 0.1.0 + '@ipld/dag-cbor': 7.0.3 + axios: 1.16.1 + multiformats: 9.9.0 + uint8arrays: 3.0.0 + zod: 3.25.76 + transitivePeerDependencies: + - debug + - supports-color + + '@did-plc/lib@0.0.4': + dependencies: + '@atproto/common': 0.1.1 + '@atproto/crypto': 0.1.0 + '@ipld/dag-cbor': 7.0.3 + axios: 1.16.1 + multiformats: 9.9.0 + uint8arrays: 3.0.0 + zod: 3.25.76 + transitivePeerDependencies: + - debug + - supports-color + + '@did-plc/server@0.0.1': + dependencies: + '@atproto/common': 0.1.0 + '@atproto/crypto': 0.1.0 + '@did-plc/lib': 0.0.1 + axios: 1.16.1 + cors: 2.8.6 + express: 4.22.2 + express-async-errors: 3.1.1(express@4.22.2) + http-terminator: 3.2.0 + kysely: 0.23.5 + multiformats: 9.9.0 + pg: 8.21.0 + pino: 8.21.0 + pino-http: 8.6.1 + transitivePeerDependencies: + - debug + - pg-native + - supports-color + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@fastify/busboy@2.1.1': {} + + '@growthbook/growthbook@1.6.5': + dependencies: + dom-mutator: 0.6.0 + + '@grpc/grpc-js@1.14.4': + dependencies: + '@grpc/proto-loader': 0.8.1 + '@js-sdsl/ordered-map': 4.4.2 + + '@grpc/proto-loader@0.7.15': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.2 + protobufjs: 7.6.1 + yargs: 17.7.2 + + '@grpc/proto-loader@0.8.1': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.2 + protobufjs: 7.6.1 + yargs: 17.7.2 + + '@hapi/accept@6.0.3': + dependencies: + '@hapi/boom': 10.0.1 + '@hapi/hoek': 11.0.7 + + '@hapi/address@5.1.1': + dependencies: + '@hapi/hoek': 11.0.7 + + '@hapi/boom@10.0.1': + dependencies: + '@hapi/hoek': 11.0.7 + + '@hapi/bourne@3.0.0': {} + + '@hapi/content@6.0.2': + dependencies: + '@hapi/boom': 10.0.1 + + '@hapi/hoek@11.0.7': {} + + '@img/sharp-darwin-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.0.4 + optional: true + + '@img/sharp-darwin-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.0.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.0.5': + optional: true + + '@img/sharp-libvips-linux-s390x@1.0.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + optional: true + + '@img/sharp-linux-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.0.4 + optional: true + + '@img/sharp-linux-arm@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.0.5 + optional: true + + '@img/sharp-linux-s390x@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.0.4 + optional: true + + '@img/sharp-linux-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + optional: true + + '@img/sharp-wasm32@0.33.5': + dependencies: + '@emnapi/runtime': 1.10.0 + optional: true + + '@img/sharp-win32-ia32@0.33.5': + optional: true + + '@img/sharp-win32-x64@0.33.5': + optional: true + + '@ioredis/commands@1.5.1': {} + + '@ipld/dag-cbor@7.0.3': + dependencies: + cborg: 1.10.2 + multiformats: 9.9.0 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@js-sdsl/ordered-map@4.4.2': {} + + '@noble/curves@1.9.7': + dependencies: + '@noble/hashes': 1.8.0 + + '@noble/hashes@1.8.0': {} + + '@noble/secp256k1@1.7.2': {} + + '@nodable/entities@2.1.0': {} + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.5': {} + + '@protobufjs/eventemitter@1.1.1': {} + + '@protobufjs/fetch@1.1.1': + dependencies: + '@protobufjs/aspromise': 1.1.2 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.2': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.1': {} + + '@smithy/abort-controller@4.2.15': + dependencies: + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@smithy/core@3.24.4': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@4.3.4': + dependencies: + '@smithy/core': 3.24.4 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@smithy/fetch-http-handler@5.4.4': + dependencies: + '@smithy/core': 3.24.4 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@smithy/is-array-buffer@2.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/middleware-endpoint@4.5.4': + dependencies: + '@smithy/core': 3.24.4 + tslib: 2.8.1 + + '@smithy/node-http-handler@4.7.4': + dependencies: + '@smithy/core': 3.24.4 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@smithy/signature-v4@5.4.4': + dependencies: + '@smithy/core': 3.24.4 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@smithy/smithy-client@4.13.4': + dependencies: + '@smithy/core': 3.24.4 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@smithy/types@4.14.2': + dependencies: + tslib: 2.8.1 + + '@smithy/util-buffer-from@2.2.0': + dependencies: + '@smithy/is-array-buffer': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-utf8@2.3.0': + dependencies: + '@smithy/util-buffer-from': 2.2.0 + tslib: 2.8.1 + + '@standard-schema/spec@1.1.0': {} + + '@tokenizer/token@0.3.0': {} + + '@ts-morph/common@0.28.1': + dependencies: + minimatch: 10.2.5 + path-browserify: 1.0.1 + tinyglobby: 0.2.16 + + '@types/bn.js@5.2.0': + dependencies: + '@types/node': 24.12.4 + + '@types/elliptic@6.4.18': + dependencies: + '@types/bn.js': 5.2.0 + + '@types/http-errors@2.0.5': {} + + '@types/node@24.12.4': + dependencies: + undici-types: 7.16.0 + + '@types/node@25.9.1': + dependencies: + undici-types: 7.24.6 + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + array-flatten@1.1.1: {} + + asn1.js@5.4.1: + dependencies: + bn.js: 4.12.3 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + safer-buffer: 2.1.2 + + asynckit@0.4.0: {} + + atomic-sleep@1.0.0: {} + + await-lock@3.0.0: {} + + axios@1.16.1: + dependencies: + follow-redirects: 1.16.0 + form-data: 4.0.5 + https-proxy-agent: 5.0.1 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + - supports-color + + balanced-match@1.0.2: {} + + balanced-match@4.0.4: {} + + base64-js@1.5.1: {} + + better-sqlite3@12.10.0: + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.3 + + big-integer@1.6.52: {} + + bignumber.js@9.3.1: {} + + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + bn.js@4.12.3: {} + + body-parser@1.20.5: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.15.1 + raw-body: 2.5.3 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + bowser@2.14.1: {} + + brace-expansion@2.1.0: + dependencies: + balanced-match: 1.0.2 + + brace-expansion@5.0.6: + dependencies: + balanced-match: 4.0.4 + + brorand@1.1.0: {} + + buffer@5.6.0: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + cborg@1.10.2: {} + + cborg@4.5.8: {} + + chownr@1.1.4: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + cluster-key-slot@1.1.2: {} + + cockatiel@3.2.1: {} + + code-block-writer@13.0.3: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.4 + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + compressible@2.0.18: + dependencies: + mime-db: 1.54.0 + + compression@1.8.1: + dependencies: + bytes: 3.1.2 + compressible: 2.0.18 + debug: 2.6.9 + negotiator: 0.6.4 + on-headers: 1.1.0 + safe-buffer: 5.2.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + cookie-signature@1.0.7: {} + + cookie@0.7.2: {} + + core-js@3.49.0: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + deep-extend@0.6.0: {} + + deepmerge@4.3.1: {} + + delay@5.0.0: {} + + delayed-stream@1.0.0: {} + + denque@2.1.0: {} + + depd@2.0.0: {} + + destroy@1.2.0: {} + + detect-libc@2.1.2: {} + + disposable-email-domains-js@1.25.0: {} + + dom-mutator@0.6.0: {} + + dom-serializer@1.4.1: + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + entities: 2.2.0 + + domelementtype@2.3.0: {} + + domhandler@4.3.1: + dependencies: + domelementtype: 2.3.0 + + domutils@2.8.0: + dependencies: + dom-serializer: 1.4.1 + domelementtype: 2.3.0 + domhandler: 4.3.1 + + dotenv@16.6.1: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + ee-first@1.1.1: {} + + elliptic@6.6.1: + dependencies: + bn.js: 4.12.3 + brorand: 1.1.0 + hash.js: 1.1.7 + hmac-drbg: 1.0.1 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + encodeurl@2.0.0: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + entities@2.2.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.3 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + etag@1.8.1: {} + + etcd3@1.1.2: + dependencies: + '@grpc/grpc-js': 1.14.4 + '@grpc/proto-loader': 0.7.15 + bignumber.js: 9.3.1 + cockatiel: 3.2.1 + + event-target-shim@5.0.1: {} + + eventemitter3@5.0.4: {} + + events@3.3.0: {} + + expand-template@2.0.3: {} + + express-async-errors@3.1.1(express@4.22.2): + dependencies: + express: 4.22.2 + + express@4.22.2: + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.5 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.0.7 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.3.2 + fresh: 0.5.2 + http-errors: 2.0.1 + merge-descriptors: 1.0.3 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.13 + proxy-addr: 2.0.7 + qs: 6.15.1 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.19.2 + serve-static: 1.16.3 + setprototypeof: 1.2.0 + statuses: 2.0.2 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fast-printf@1.6.10: {} + + fast-redact@3.5.0: {} + + fast-xml-builder@1.2.0: + dependencies: + path-expression-matcher: 1.5.0 + xml-naming: 0.1.0 + + fast-xml-parser@5.7.3: + dependencies: + '@nodable/entities': 2.1.0 + fast-xml-builder: 1.2.0 + path-expression-matcher: 1.5.0 + strnum: 2.3.0 + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + file-type@16.5.4: + dependencies: + readable-web-to-node-stream: 3.0.4 + strtok3: 6.3.0 + token-types: 4.2.1 + + file-uri-to-path@1.0.0: {} + + finalhandler@1.3.2: + dependencies: + debug: 2.6.9 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + follow-redirects@1.16.0: {} + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.3 + mime-types: 2.1.35 + + forwarded@0.2.0: {} + + french-badwords-list@1.0.7: + optional: true + + fresh@0.5.2: {} + + fs-constants@1.0.0: {} + + function-bind@1.1.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.3 + math-intrinsics: 1.1.0 + + get-port@5.1.1: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + github-from-package@0.0.0: {} + + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + gopd@1.2.0: {} + + handlebars@4.7.9: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hash.js@1.1.7: + dependencies: + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + + hasown@2.0.3: + dependencies: + function-bind: 1.1.2 + + he@1.2.0: {} + + hmac-drbg@1.0.1: + dependencies: + hash.js: 1.1.7 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 + + html-to-text@7.1.1: + dependencies: + deepmerge: 4.3.1 + he: 1.2.0 + htmlparser2: 6.1.0 + minimist: 1.2.8 + + htmlparser2@6.1.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + domutils: 2.8.0 + entities: 2.2.0 + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + http-terminator@3.2.0: + dependencies: + delay: 5.0.0 + p-wait-for: 3.2.0 + roarr: 7.21.5 + type-fest: 2.19.0 + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + inherits@2.0.4: {} + + ini@1.3.8: {} + + ioredis@5.10.1: + dependencies: + '@ioredis/commands': 1.5.1 + cluster-key-slot: 1.1.2 + debug: 4.4.3 + denque: 2.1.0 + lodash.defaults: 4.2.0 + lodash.isarguments: 3.1.0 + redis-errors: 1.2.0 + redis-parser: 3.0.0 + standard-as-callback: 2.1.0 + transitivePeerDependencies: + - supports-color + + ipaddr.js@1.9.1: {} + + ipaddr.js@2.4.0: {} + + is-arrayish@0.3.4: {} + + is-fullwidth-code-point@3.0.0: {} + + isexe@2.0.0: {} + + iso-datestring-validator@2.2.2: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jose@5.10.0: {} + + key-encoder@2.0.3: + dependencies: + '@types/elliptic': 6.4.18 + asn1.js: 5.4.1 + bn.js: 4.12.3 + elliptic: 6.6.1 + + kysely@0.22.0: {} + + kysely@0.23.5: {} + + lande@1.0.10: + dependencies: + toygrad: 2.6.0 + + leo-profanity@1.9.0: + optionalDependencies: + french-badwords-list: 1.0.7 + russian-bad-words: 0.5.0 + + lodash.camelcase@4.3.0: {} + + lodash.defaults@4.2.0: {} + + lodash.isarguments@3.1.0: {} + + long@5.3.2: {} + + lru-cache@10.4.3: {} + + math-intrinsics@1.1.0: {} + + media-typer@0.3.0: {} + + merge-descriptors@1.0.3: {} + + methods@1.1.2: {} + + mime-db@1.52.0: {} + + mime-db@1.54.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@1.6.0: {} + + mimic-response@3.1.0: {} + + minimalistic-assert@1.0.1: {} + + minimalistic-crypto-utils@1.0.1: {} + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.6 + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.1.0 + + minimist@1.2.8: {} + + minipass@7.1.3: {} + + mkdirp-classic@0.5.3: {} + + ms@2.0.0: {} + + ms@2.1.3: {} + + multiformats@13.4.2: {} + + multiformats@9.9.0: {} + + murmurhash@2.0.1: {} + + napi-build-utils@2.0.0: {} + + negotiator@0.6.3: {} + + negotiator@0.6.4: {} + + neo-async@2.6.2: {} + + node-abi@3.92.0: + dependencies: + semver: 7.8.0 + + nodemailer-html-to-text@3.2.0: + dependencies: + html-to-text: 7.1.1 + + nodemailer@6.10.1: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + on-exit-leak-free@2.1.2: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + on-headers@1.1.0: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + one-webcrypto@1.0.3: {} + + p-finally@1.0.0: {} + + p-queue@8.1.1: + dependencies: + eventemitter3: 5.0.4 + p-timeout: 6.1.4 + + p-timeout@3.2.0: + dependencies: + p-finally: 1.0.0 + + p-timeout@6.1.4: {} + + p-wait-for@3.2.0: + dependencies: + p-timeout: 3.2.0 + + package-json-from-dist@1.0.1: {} + + parseurl@1.3.3: {} + + path-browserify@1.0.1: {} + + path-expression-matcher@1.5.0: {} + + path-key@3.1.1: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + + path-to-regexp@0.1.13: {} + + peek-readable@4.1.0: {} + + pg-cloudflare@1.4.0: + optional: true + + pg-connection-string@2.13.0: {} + + pg-int8@1.0.1: {} + + pg-pool@3.14.0(pg@8.21.0): + dependencies: + pg: 8.21.0 + + pg-protocol@1.14.0: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.1 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + pg@8.21.0: + dependencies: + pg-connection-string: 2.13.0 + pg-pool: 3.14.0(pg@8.21.0) + pg-protocol: 1.14.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.4.0 + + pgpass@1.0.5: + dependencies: + split2: 4.2.0 + + picomatch@4.0.4: {} + + pino-abstract-transport@1.2.0: + dependencies: + readable-stream: 4.7.0 + split2: 4.2.0 + + pino-http@8.6.1: + dependencies: + get-caller-file: 2.0.5 + pino: 8.21.0 + pino-std-serializers: 6.2.2 + process-warning: 3.0.0 + + pino-std-serializers@6.2.2: {} + + pino@8.21.0: + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.5.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 1.2.0 + pino-std-serializers: 6.2.2 + process-warning: 3.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 3.8.1 + thread-stream: 2.7.0 + + postgres-array@2.0.0: {} + + postgres-bytea@1.0.1: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.1.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.92.0 + pump: 3.0.4 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.4 + tunnel-agent: 0.6.0 + + prettier@3.8.3: {} + + process-warning@3.0.0: {} + + process@0.11.10: {} + + protobufjs@7.6.1: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.5 + '@protobufjs/eventemitter': 1.1.1 + '@protobufjs/fetch': 1.1.1 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.2 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.1 + '@types/node': 24.12.4 + long: 5.3.2 + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + proxy-from-env@2.1.0: {} + + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + qs@6.15.1: + dependencies: + side-channel: 1.1.0 + + quick-format-unescaped@4.0.4: {} + + range-parser@1.2.1: {} + + rate-limiter-flexible@2.4.2: {} + + raw-body@2.5.3: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readable-stream@4.7.0: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + + readable-web-to-node-stream@3.0.4: + dependencies: + readable-stream: 4.7.0 + + real-require@0.2.0: {} + + redis-errors@1.2.0: {} + + redis-parser@3.0.0: + dependencies: + redis-errors: 1.2.0 + + require-directory@2.1.1: {} + + roarr@7.21.5: + dependencies: + fast-printf: 1.6.10 + safe-stable-stringify: 2.5.0 + semver-compare: 1.0.0 + + russian-bad-words@0.5.0: + optional: true + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + optional: true + + safe-buffer@5.2.1: {} + + safe-stable-stringify@2.5.0: {} + + safer-buffer@2.1.2: {} + + semver-compare@1.0.0: {} + + semver@7.8.0: {} + + send@0.19.2: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.1 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@1.16.3: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.2 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + sharp@0.33.5: + dependencies: + color: 4.2.3 + detect-libc: 2.1.2 + semver: 7.8.0 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.33.5 + '@img/sharp-darwin-x64': 0.33.5 + '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-linux-arm': 0.33.5 + '@img/sharp-linux-arm64': 0.33.5 + '@img/sharp-linux-s390x': 0.33.5 + '@img/sharp-linux-x64': 0.33.5 + '@img/sharp-linuxmusl-arm64': 0.33.5 + '@img/sharp-linuxmusl-x64': 0.33.5 + '@img/sharp-wasm32': 0.33.5 + '@img/sharp-win32-ia32': 0.33.5 + '@img/sharp-win32-x64': 0.33.5 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@4.1.0: {} + + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + + simple-swizzle@0.2.4: + dependencies: + is-arrayish: 0.3.4 + + sonic-boom@3.8.1: + dependencies: + atomic-sleep: 1.0.0 + + source-map@0.6.1: {} + + split2@4.2.0: {} + + standard-as-callback@2.1.0: {} + + statuses@2.0.2: {} + + stream-browserify@3.0.0: + dependencies: + inherits: 2.0.4 + readable-stream: 3.6.2 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + strip-json-comments@2.0.1: {} + + strnum@2.3.0: {} + + strtok3@6.3.0: + dependencies: + '@tokenizer/token': 0.3.0 + peek-readable: 4.1.0 + + structured-headers@1.0.1: {} + + tar-fs@2.1.4: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.4 + tar-stream: 2.2.0 + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + thread-stream@2.7.0: + dependencies: + real-require: 0.2.0 + + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tlds@1.261.0: {} + + toidentifier@1.0.1: {} + + token-types@4.2.1: + dependencies: + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + + toygrad@2.6.0: {} + + ts-morph@27.0.2: + dependencies: + '@ts-morph/common': 0.28.1 + code-block-writer: 13.0.3 + + tslib@2.8.1: {} + + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + + type-fest@2.19.0: {} + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + typed-emitter@2.1.0: + optionalDependencies: + rxjs: 7.8.2 + + typescript@6.0.3: {} + + uglify-js@3.19.3: + optional: true + + uint8arrays@3.0.0: + dependencies: + multiformats: 9.9.0 + + uint8arrays@5.1.1: + dependencies: + multiformats: 13.4.2 + + undici-types@7.16.0: {} + + undici-types@7.24.6: {} + + undici@5.29.0: + dependencies: + '@fastify/busboy': 2.1.1 + + undici@6.25.0: {} + + unicode-segmenter@0.14.5: {} + + unpipe@1.0.0: {} + + util-deprecate@1.0.2: {} + + utils-merge@1.0.1: {} + + varint@6.0.0: {} + + vary@1.1.2: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wordwrap@1.0.0: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + + wrappy@1.0.2: {} + + ws@8.20.1: {} + + xml-naming@0.1.0: {} + + xtend@4.0.2: {} + + y18n@5.0.8: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + zod@3.23.8: {} + + zod@3.25.76: {} diff --git a/dev-env/pnpm-workspace.yaml b/dev-env/pnpm-workspace.yaml new file mode 100644 index 0000000000..b228457beb --- /dev/null +++ b/dev-env/pnpm-workspace.yaml @@ -0,0 +1,9 @@ +allowBuilds: + 'better-sqlite3': true + 'core-js': true + 'protobufjs': true + 'sharp': true + 'unrs-resolver': true +minimumReleaseAgeExclude: + - '@atproto/*' + - '@atproto-labs/*' diff --git a/dev-env/test-pds.ts b/dev-env/test-pds.ts index 98933a0633..6696868866 100644 --- a/dev-env/test-pds.ts +++ b/dev-env/test-pds.ts @@ -1,8 +1,9 @@ +import fs from 'node:fs' +import net from 'node:net' +import path from 'node:path' + import {AtUri, BskyAgent} from '@atproto/api' import {type TestBsky, TestNetwork} from '@atproto/dev-env' -import fs from 'fs' -import net from 'net' -import path from 'path' export interface TestUser { email: string @@ -21,9 +22,11 @@ export interface TestPDS { class StringIdGenerator { _nextId = [0] - constructor( - public _chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', - ) {} + _chars: string + + constructor(chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') { + this._chars = chars + } next() { const r = [] @@ -83,13 +86,13 @@ export async function createServer( // DISABLED - looks like dev-env added this and now it conflicts // add the test mod authority // const agent = new BskyAgent({service: pdsUrl}) - // const res = await agent.api.com.atproto.server.createAccount({ + // const res = await agent.com.atproto.server.createAccount({ // email: 'mod-authority@test.com', // handle: 'mod-authority.test', // password: 'hunter2', // }) - // agent.api.setHeader('Authorization', `Bearer ${res.data.accessJwt}`) - // await agent.api.app.bsky.actor.profile.create( + // agent.setHeader('Authorization', `Bearer ${res.data.accessJwt}`) + // await agent.app.bsky.actor.profile.create( // {repo: res.data.did}, // { // displayName: 'Dev-env Moderation', @@ -97,7 +100,7 @@ export async function createServer( // }, // ) - // await agent.api.app.bsky.labeler.service.create( + // await agent.app.bsky.labeler.service.create( // {repo: res.data.did, rkey: 'self'}, // { // policies: { @@ -109,7 +112,7 @@ export async function createServer( // ) const pic = fs.readFileSync( - path.join(__dirname, '..', 'assets', 'default-avatar.png'), + path.join(import.meta.dirname, '..', 'assets', 'default-avatar.png'), ) return { @@ -125,12 +128,14 @@ export async function createServer( class Mocker { agent: BskyAgent users: Record = {} + testNet: TestNetwork + service: string + pic: Uint8Array - constructor( - public testNet: TestNetwork, - public service: string, - public pic: Uint8Array, - ) { + constructor(testNet: TestNetwork, service: string, pic: Uint8Array) { + this.testNet = testNet + this.service = service + this.pic = pic this.agent = new BskyAgent({service}) } @@ -161,7 +166,7 @@ class Mocker { async createUser(name: string) { const agent = new BskyAgent({service: this.service}) - const inviteRes = await agent.api.com.atproto.server.createInviteCode( + const inviteRes = await agent.com.atproto.server.createInviteCode( {useCount: 1}, { headers: this.pds.adminAuthHeaders(), @@ -309,20 +314,21 @@ class Mocker { 'app.bsky.feed.generator', rkey, ) + // @ts-expect-error const fg1 = await this.testNet.createFeedGen({ - [fgUri.toString()]: async () => { + [fgUri.toString()]: () => { return { - encoding: 'application/json', + encoding: 'application/json' as const, body: { feed: posts.slice(0, 30).map(uri => ({post: uri})), }, } }, }) - const avatarRes = await agent.api.com.atproto.repo.uploadBlob(this.pic, { + const avatarRes = await agent.com.atproto.repo.uploadBlob(this.pic, { encoding: 'image/png', }) - return await agent.api.app.bsky.feed.generator.create( + return await agent.app.bsky.feed.generator.create( {repo: this.users[user].did, rkey}, { did: fg1.did, @@ -336,7 +342,7 @@ class Mocker { async createInvite(forAccount: string) { const agent = new BskyAgent({service: this.service}) - await agent.api.com.atproto.server.createInviteCode( + await agent.com.atproto.server.createInviteCode( {useCount: 1, forAccount}, { headers: this.pds.adminAuthHeaders(), diff --git a/dev-env/tsconfig.json b/dev-env/tsconfig.json index d245dde8ec..b47f275254 100644 --- a/dev-env/tsconfig.json +++ b/dev-env/tsconfig.json @@ -1,47 +1,17 @@ { "compilerOptions": { "target": "esnext", - "module": "commonjs", - "types": ["react-native", "jest"], - "lib": [ - "es2019", - "es2020.bigint", - "es2020.date", - "es2020.number", - "es2020.promise", - "es2020.string", - "es2020.symbol.wellknown", - "es2021.promise", - "es2021.string", - "es2021.weakref", - "es2022.array", - "es2022.object", - "es2022.string" - ], - "allowJs": true, - "jsx": "react-native", + "module": "nodenext", + "moduleResolution": "nodenext", + "types": ["node"], + "lib": ["esnext"], "noEmit": true, "isolatedModules": true, "strict": true, - "moduleResolution": "node", - "resolveJsonModule": true, - "allowSyntheticDefaultImports": true, - "forceConsistentCasingInFileNames": false, "esModuleInterop": true, "skipLibCheck": true, - "paths": { - "#/*": ["./src/*"], - "lib/*": ["./src/lib/*"], - "platform/*": ["./src/platform/*"], - "state/*": ["./src/state/*"], - "view/*": ["./src/view/*"], - "crypto": ["./src/platform/crypto.ts"] - } + "erasableSyntaxOnly": true, + "allowImportingTsExtensions": true }, - "exclude": [ - "node_modules", - "babel.config.js", - "metro.config.js", - "jest.config.js" - ] + "exclude": ["node_modules"] } diff --git a/dev-env/yarn.lock b/dev-env/yarn.lock deleted file mode 100644 index d95277a11d..0000000000 --- a/dev-env/yarn.lock +++ /dev/null @@ -1,4424 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@atproto-labs/did-resolver@^0.2.6": - version "0.2.6" - resolved "https://registry.yarnpkg.com/@atproto-labs/did-resolver/-/did-resolver-0.2.6.tgz#15f0beab797187a67279389f6503f87a257cd898" - integrity sha512-2K1bC04nI2fmgNcvof+yA28IhGlpWn2JKYlPa7To9JTKI45FINCGkQSGiL2nyXlyzDJJ34fZ1aq6/IRFIOIiqg== - dependencies: - "@atproto-labs/fetch" "0.2.3" - "@atproto-labs/pipe" "0.1.1" - "@atproto-labs/simple-store" "0.3.0" - "@atproto-labs/simple-store-memory" "0.1.4" - "@atproto/did" "0.3.0" - zod "^3.23.8" - -"@atproto-labs/fetch-node@^0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@atproto-labs/fetch-node/-/fetch-node-0.2.0.tgz#438989f3165f52e21e7636fb87ea9c7317ae7f2a" - integrity sha512-Krq09nH/aeoiU2s9xdHA0FjTEFWG9B5FFenipv1iRixCcPc7V3DhTNDawxG9gI8Ny0k4dBVS9WTRN/IDzBx86Q== - dependencies: - "@atproto-labs/fetch" "0.2.3" - "@atproto-labs/pipe" "0.1.1" - ipaddr.js "^2.1.0" - undici "^6.14.1" - -"@atproto-labs/fetch@0.2.3", "@atproto-labs/fetch@^0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@atproto-labs/fetch/-/fetch-0.2.3.tgz#d47afec078f630c50e291c56264cc0ff13d0c6cc" - integrity sha512-NZtbJOCbxKUFRFKMpamT38PUQMY0hX0p7TG5AEYOPhZKZEP7dHZ1K2s1aB8MdVH0qxmqX7nQleNrrvLf09Zfdw== - dependencies: - "@atproto-labs/pipe" "0.1.1" - -"@atproto-labs/pipe@0.1.1", "@atproto-labs/pipe@^0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@atproto-labs/pipe/-/pipe-0.1.1.tgz#1c4232d16bf95f251e993cb6ee440f9aa4e87ce6" - integrity sha512-hdNw2oUs2B6BN1lp+32pF7cp8EMKuIN5Qok2Vvv/aOpG/3tNSJ9YkvfI0k6Zd188LeDDYRUpYpxcoFIcGH/FNg== - -"@atproto-labs/simple-store-memory@0.1.4", "@atproto-labs/simple-store-memory@^0.1.4": - version "0.1.4" - resolved "https://registry.yarnpkg.com/@atproto-labs/simple-store-memory/-/simple-store-memory-0.1.4.tgz#e38c7b27e0f77c0bdba1329deb89593fbec27316" - integrity sha512-3mKY4dP8I7yKPFj9VKpYyCRzGJOi5CEpOLPlRhoJyLmgs3J4RzDrjn323Oakjz2Aj2JzRU/AIvWRAZVhpYNJHw== - dependencies: - "@atproto-labs/simple-store" "0.3.0" - lru-cache "^10.2.0" - -"@atproto-labs/simple-store-redis@^0.0.1": - version "0.0.1" - resolved "https://registry.yarnpkg.com/@atproto-labs/simple-store-redis/-/simple-store-redis-0.0.1.tgz#1dfc92cbec9b648c4349255aebb7bce9d7dc5eeb" - integrity sha512-hGkfDNVtTqwcRx27k6u25pgwNIHq3xDCRuojkfHf6c1B9R5rKphdZJ91Mn3lCvsyDB/lUqqLuzKuXQWFml/u5g== - dependencies: - "@atproto-labs/simple-store" "0.3.0" - -"@atproto-labs/simple-store@0.3.0", "@atproto-labs/simple-store@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@atproto-labs/simple-store/-/simple-store-0.3.0.tgz#65c0a5c949fe6c8dc3bdaf13ab40848f20073593" - integrity sha512-nOb6ONKBRJHRlukW1sVawUkBqReLlLx6hT35VS3imaNPwiXDxLnTK7lxw3Lrl9k5yugSBDQAkZAq3MPTEFSUBQ== - -"@atproto-labs/xrpc-utils@^0.0.24": - version "0.0.24" - resolved "https://registry.yarnpkg.com/@atproto-labs/xrpc-utils/-/xrpc-utils-0.0.24.tgz#0546778b9b83854d8a160dc4dea145e5a23ae8fc" - integrity sha512-wWXd2Ht47UsL/UbDCr3twMFSZrh0xSI56u4O3kz0DTU4G+530mCG71mMVE6eeYcR+j6FEjp7o2Ld6c7wFklYGw== - dependencies: - "@atproto/xrpc" "^0.7.6" - "@atproto/xrpc-server" "^0.10.0" - -"@atproto/api@^0.19.4": - version "0.19.4" - resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.19.4.tgz#f3ff850baf4d85538c082fb91aa0982737eb68be" - integrity sha512-fYNM62vdXxer0h8a9Jzl4/ag9uFIe0nTO+LkC6KTlx1yUDigrAoQMMbllIiCWj62GhUMxAkHabk/BZjjVAfKng== - dependencies: - "@atproto/common-web" "^0.4.18" - "@atproto/lexicon" "^0.6.2" - "@atproto/syntax" "^0.5.1" - "@atproto/xrpc" "^0.7.7" - await-lock "^2.2.2" - multiformats "^9.9.0" - tlds "^1.234.0" - zod "^3.23.8" - -"@atproto/aws@^0.2.31": - version "0.2.31" - resolved "https://registry.yarnpkg.com/@atproto/aws/-/aws-0.2.31.tgz#e46d7db34ee57c4f9817269f1e73a7eddba2b9b8" - integrity sha512-jzw0v4KttNqeEZtkIujIOCvWABJl4m8WQuSOlJtYeJhn/xaBC1+CNt7qnvYCzBd+T5nncZU8junHSEbOMdVwiA== - dependencies: - "@atproto/common" "^0.5.0" - "@atproto/common-web" "^0.4.4" - "@atproto/crypto" "^0.4.4" - "@atproto/repo" "^0.8.11" - "@aws-sdk/client-cloudfront" "^3.879.0" - "@aws-sdk/client-kms" "^3.879.0" - "@aws-sdk/client-s3" "^3.879.0" - "@aws-sdk/lib-storage" "3.879.0" - "@noble/curves" "^1.7.0" - key-encoder "^2.0.3" - multiformats "^9.9.0" - uint8arrays "3.0.0" - -"@atproto/bsky@^0.0.221": - version "0.0.221" - resolved "https://registry.yarnpkg.com/@atproto/bsky/-/bsky-0.0.221.tgz#b574456225db66c866848526947473d60bb4d0e7" - integrity sha512-feNR6xkJ9HCJbQdJU3ytsnAfaSBXJdaXIMi0tNPrsLfoqCWWbTfXIcMczXeY4SOhKGFR5oMYxE9zrRC/TTAssw== - dependencies: - "@atproto-labs/fetch-node" "^0.2.0" - "@atproto-labs/xrpc-utils" "^0.0.24" - "@atproto/api" "^0.19.4" - "@atproto/common" "^0.5.15" - "@atproto/crypto" "^0.4.5" - "@atproto/did" "^0.3.0" - "@atproto/identity" "^0.4.12" - "@atproto/lexicon" "^0.6.2" - "@atproto/repo" "^0.8.13" - "@atproto/sync" "^0.1.40" - "@atproto/syntax" "^0.5.1" - "@atproto/xrpc-server" "^0.10.17" - "@bufbuild/protobuf" "^1.5.0" - "@connectrpc/connect" "^1.1.4" - "@connectrpc/connect-express" "^1.1.4" - "@connectrpc/connect-node" "^1.1.4" - "@did-plc/lib" "^0.0.1" - "@growthbook/growthbook" "^1.6.2" - "@hapi/address" "^5.1.1" - "@types/http-errors" "^2.0.1" - compression "^1.7.4" - cors "^2.8.5" - disposable-email-domains-js "^1.5.0" - etcd3 "^1.1.2" - express "^4.17.2" - http-errors "^2.0.0" - http-terminator "^3.2.0" - ioredis "^5.3.2" - jose "^5.0.1" - key-encoder "^2.0.3" - kysely "^0.22.0" - leo-profanity "^1.8.0" - multiformats "^9.9.0" - murmurhash "^2.0.1" - p-queue "^6.6.2" - pg "^8.10.0" - pino "^8.21.0" - pino-http "^8.2.1" - sharp "^0.33.5" - structured-headers "^1.0.1" - typed-emitter "^2.1.0" - uint8arrays "3.0.0" - undici "^6.19.8" - zod "3.23.8" - -"@atproto/bsync@^0.0.25": - version "0.0.25" - resolved "https://registry.yarnpkg.com/@atproto/bsync/-/bsync-0.0.25.tgz#0d6056f844c0b2579d9dfc04b6727974dc03fcd6" - integrity sha512-5tjP5QbUcNtMBw7FJeyRfA0OHQRKrg97Jva6Q26cKqLMICGYNbx0fpD7nZhGTP2/s1gd6UZkG9Zdh4GRZpbxWg== - dependencies: - "@atproto/common" "^0.5.14" - "@atproto/syntax" "^0.5.1" - "@bufbuild/protobuf" "^1.5.0" - "@connectrpc/connect" "^1.1.4" - "@connectrpc/connect-node" "^1.1.4" - http-terminator "^3.2.0" - kysely "^0.22.0" - pg "^8.10.0" - pino-http "^8.2.1" - typed-emitter "^2.1.0" - -"@atproto/common-web@^0.4.17", "@atproto/common-web@^0.4.18", "@atproto/common-web@^0.4.4", "@atproto/common-web@^0.4.7": - version "0.4.18" - resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.4.18.tgz#832976340457afd3d29345ad2c6f0ac0bff087dd" - integrity sha512-ilImzP+9N/mtse440kN60pGrEzG7wi4xsV13nGeLrS+Zocybc/ISOpKlbZM13o+twPJ+Q7veGLw9CtGg0GAFoQ== - dependencies: - "@atproto/lex-data" "^0.0.13" - "@atproto/lex-json" "^0.0.13" - "@atproto/syntax" "^0.5.0" - zod "^3.23.8" - -"@atproto/common-web@^0.4.19": - version "0.4.19" - resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.4.19.tgz#bbd7f84f545ebe73ca3bc00314ccf4ee66e7069e" - integrity sha512-3BTi58p5WpT+9/zb6UZrdsXcfPo5P45UJm0E4iwHLILr+jc37CuBj9JReDSZ4U0i9RTrI3ZkfySyZ9bd+LnMsw== - dependencies: - "@atproto/lex-data" "^0.0.14" - "@atproto/lex-json" "^0.0.14" - "@atproto/syntax" "^0.5.1" - zod "^3.23.8" - -"@atproto/common@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.1.0.tgz#4216a8fef5b985ab62ac21252a0f8ca0f4a0f210" - integrity sha512-OB5tWE2R19jwiMIs2IjQieH5KTUuMb98XGCn9h3xuu6NanwjlmbCYMv08fMYwIp3UQ6jcq//84cDT3Bu6fJD+A== - dependencies: - "@ipld/dag-cbor" "^7.0.3" - multiformats "^9.6.4" - pino "^8.6.1" - zod "^3.14.2" - -"@atproto/common@0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.1.1.tgz#ec33a3b4995c91d3ad2e90fc4cdbc65284ceff84" - integrity sha512-GYwot5wF/z8iYGSPjrLHuratLc0CVgovmwfJss7+BUOB6y2/Vw8+1Vw0n9DDI0gb5vmx3UI8z0uJgC8aa8yuJg== - dependencies: - "@ipld/dag-cbor" "^7.0.3" - multiformats "^9.6.4" - pino "^8.6.1" - zod "^3.14.2" - -"@atproto/common@^0.5.0", "@atproto/common@^0.5.14", "@atproto/common@^0.5.3": - version "0.5.14" - resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.5.14.tgz#e7281d07587f4a6b79ab98d23d0a4c3451798864" - integrity sha512-FnhTppvJw8I1AuvEkL9JREFwmM6ciYfSlQ0Zo6neiJIhTf1wf5/ONeFSYKu1/dxC63JEratGIAfVjSBJJZi7sg== - dependencies: - "@atproto/common-web" "^0.4.18" - "@atproto/lex-cbor" "^0.0.14" - "@atproto/lex-data" "^0.0.13" - multiformats "^9.9.0" - pino "^8.21.0" - -"@atproto/common@^0.5.15": - version "0.5.15" - resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.5.15.tgz#3c43c25d3493d868cc4281d6ac2f923b00644463" - integrity sha512-+cdfdMPAIbH9zQGLfH1gNY2KEZsMxj0EelVQL5uJUFL+UkkAXiiqWj7J5mbax8sf02cC/afJnfkWzERNAheKoA== - dependencies: - "@atproto/common-web" "^0.4.19" - "@atproto/lex-cbor" "^0.0.15" - "@atproto/lex-data" "^0.0.14" - multiformats "^9.9.0" - pino "^8.21.0" - -"@atproto/crypto@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@atproto/crypto/-/crypto-0.1.0.tgz#bc73a479f9dbe06fa025301c182d7f7ab01bc568" - integrity sha512-9xgFEPtsCiJEPt9o3HtJT30IdFTGw5cQRSJVIy5CFhqBA4vDLcdXiRDLCjkzHEVbtNCsHUW6CrlfOgbeLPcmcg== - dependencies: - "@noble/secp256k1" "^1.7.0" - big-integer "^1.6.51" - multiformats "^9.6.4" - one-webcrypto "^1.0.3" - uint8arrays "3.0.0" - -"@atproto/crypto@^0.4.4", "@atproto/crypto@^0.4.5": - version "0.4.5" - resolved "https://registry.yarnpkg.com/@atproto/crypto/-/crypto-0.4.5.tgz#fc6ad4fdfe8338147196c8050791cc6a22657eb6" - integrity sha512-n40aKkMoCatP0u9Yvhrdk6fXyOHFDDbkdm4h4HCyWW+KlKl8iXfD5iV+ECq+w5BM+QH25aIpt3/j6EUNerhLxw== - dependencies: - "@noble/curves" "^1.7.0" - "@noble/hashes" "^1.6.1" - uint8arrays "3.0.0" - -"@atproto/dev-env@^0.3.215": - version "0.3.215" - resolved "https://registry.yarnpkg.com/@atproto/dev-env/-/dev-env-0.3.215.tgz#9da8c4a73abb4501ac72c73da9536e2ec86cb46f" - integrity sha512-zwZwGWYLgP2Zdie6/gMtxuDbSs7/UV/gPJYfOlXln1ZDoMkfFbgCTq44PWRnJpUKTzrq7gt19E0GsL7DMkppjA== - dependencies: - "@atproto/api" "^0.19.4" - "@atproto/bsky" "^0.0.221" - "@atproto/bsync" "^0.0.25" - "@atproto/common-web" "^0.4.19" - "@atproto/crypto" "^0.4.5" - "@atproto/identity" "^0.4.12" - "@atproto/lexicon" "^0.6.2" - "@atproto/ozone" "^0.1.167" - "@atproto/pds" "^0.4.216" - "@atproto/sync" "^0.1.40" - "@atproto/syntax" "^0.5.1" - "@atproto/xrpc-server" "^0.10.17" - "@did-plc/lib" "^0.0.1" - "@did-plc/server" "^0.0.1" - dotenv "^16.0.3" - express "^4.18.2" - get-port "^5.1.1" - multiformats "^9.9.0" - uint8arrays "3.0.0" - undici "^6.14.1" - -"@atproto/did@0.3.0", "@atproto/did@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@atproto/did/-/did-0.3.0.tgz#0f6b11a5119672a41075fa58e97956b296ac171c" - integrity sha512-raUPzUGegtW/6OxwCmM8bhZvuIMzxG5t9oWsth6Tp91Kb5fTnHV2h/KKNF1C82doeA4BdXCErTyg7ISwLbQkzA== - dependencies: - zod "^3.23.8" - -"@atproto/identity@^0.4.12": - version "0.4.12" - resolved "https://registry.yarnpkg.com/@atproto/identity/-/identity-0.4.12.tgz#ead1312677a60d162e6d0df8fe5fda4727b5c15a" - integrity sha512-P+Jn0HvKhIh1tps5n3xGrCxt+XiFWzp4kdgloyFhFmVLwjDU547DQkWx4r5Vhuiah7fRZGVSlk39R4U6SPrACg== - dependencies: - "@atproto/common-web" "^0.4.17" - "@atproto/crypto" "^0.4.5" - -"@atproto/jwk-jose@^0.1.11": - version "0.1.11" - resolved "https://registry.yarnpkg.com/@atproto/jwk-jose/-/jwk-jose-0.1.11.tgz#ef64bce940a66e267fc3cf0db8df4dbd062bb28a" - integrity sha512-i4Fnr2sTBYmMmHXl7NJh8GrCH+tDQEVWrcDMDnV5DjJfkgT17wIqvojIw9SNbSL4Uf0OtfEv6AgG0A+mgh8b5Q== - dependencies: - "@atproto/jwk" "0.6.0" - jose "^5.2.0" - -"@atproto/jwk@0.6.0", "@atproto/jwk@^0.6.0": - version "0.6.0" - resolved "https://registry.yarnpkg.com/@atproto/jwk/-/jwk-0.6.0.tgz#e813f77d9c89c025d4074340777fafaa2fba08a5" - integrity sha512-bDoJPvt7TrQVi/rBfBrSSpGykhtIriKxeYCYQTiPRKFfyRhbgpElF0wPXADjIswnbzZdOwbY63az4E/CFVT3Tw== - dependencies: - multiformats "^9.9.0" - zod "^3.23.8" - -"@atproto/lex-cbor@^0.0.14": - version "0.0.14" - resolved "https://registry.yarnpkg.com/@atproto/lex-cbor/-/lex-cbor-0.0.14.tgz#8e378095585f5a86a074e87b82e1c4b803ebb2a6" - integrity sha512-zeqxaKAifR8qlFKg4A6t1RCT8TcjeDnIXLtp3QnDu0QoxslxsmcsrqNrrgmka8w+bYW2+h/rT9MPWglkT7vHyw== - dependencies: - "@atproto/lex-data" "^0.0.13" - tslib "^2.8.1" - -"@atproto/lex-cbor@^0.0.15": - version "0.0.15" - resolved "https://registry.yarnpkg.com/@atproto/lex-cbor/-/lex-cbor-0.0.15.tgz#ae4558d8ce22119710ad22feb458d22774b3ca3b" - integrity sha512-3osDicK9bAMXJlKjLKqwYrhLQ60bOguWBNjE+fuNjMuizNzC0aqaClE3d+qMsFuFq9bjEHFw+4Vr9Qmd/m6VYg== - dependencies: - "@atproto/lex-data" "^0.0.14" - tslib "^2.8.1" - -"@atproto/lex-client@^0.0.15": - version "0.0.15" - resolved "https://registry.yarnpkg.com/@atproto/lex-client/-/lex-client-0.0.15.tgz#c647d14e91ca3f52feebf4b34f80abb7e93b3bee" - integrity sha512-j/eZGCdkhABU8Z868Y/gn909hS77rOCdMqtOaQdflEaKUKiAo2/gqeTpoAjHBnL5Rzz255wj9qZMqZTR/Ygwxw== - dependencies: - "@atproto/lex-data" "^0.0.13" - "@atproto/lex-json" "^0.0.13" - "@atproto/lex-schema" "^0.0.14" - tslib "^2.8.1" - -"@atproto/lex-client@^0.0.17": - version "0.0.17" - resolved "https://registry.yarnpkg.com/@atproto/lex-client/-/lex-client-0.0.17.tgz#566689a288f8b2af31f4a0fa081496dfaaef7278" - integrity sha512-lZ9clUjWgpno1XhSawQP+1/JeIYA9qBh759b/NSU0OiypQqgq7IxDvmzaBsiHK1sqjo0tyEkmG4X5Ym7YXjv0Q== - dependencies: - "@atproto/lex-data" "^0.0.14" - "@atproto/lex-json" "^0.0.14" - "@atproto/lex-schema" "^0.0.16" - tslib "^2.8.1" - -"@atproto/lex-data@^0.0.13": - version "0.0.13" - resolved "https://registry.yarnpkg.com/@atproto/lex-data/-/lex-data-0.0.13.tgz#db1bcfa12d5056210f6eb7f3b8bac909909d6b9c" - integrity sha512-7Z7RwZ1Y/JzBF/Tcn/I4UJ/vIGfh5zn1zjv0KX+flke2JtgFkSE8uh2hOtqgBQMNqE3zdJFM+dcSWln86hR3MQ== - dependencies: - multiformats "^9.9.0" - tslib "^2.8.1" - uint8arrays "3.0.0" - unicode-segmenter "^0.14.0" - -"@atproto/lex-data@^0.0.14": - version "0.0.14" - resolved "https://registry.yarnpkg.com/@atproto/lex-data/-/lex-data-0.0.14.tgz#2f2f3c64699925a0d4785e5afd0e7731ba1d46c0" - integrity sha512-53DUa9664SS76nGAMYopWsO10OH0AAdf7P/HSKB6Wzx3iqe6lk/K61QZnKxOG1LreYl5CfvIJU6eNf4txI6GlQ== - dependencies: - multiformats "^9.9.0" - tslib "^2.8.1" - uint8arrays "3.0.0" - unicode-segmenter "^0.14.0" - -"@atproto/lex-document@^0.0.17": - version "0.0.17" - resolved "https://registry.yarnpkg.com/@atproto/lex-document/-/lex-document-0.0.17.tgz#8460096235910bf5ec8305f03a8da6bab8d8a12b" - integrity sha512-rQiDCSYQwze4+kaArUtmp4bjZ9rV3vYUMhjdDwmZCKodpppNEYrP5AQzyKlxBtKO+MRdLYwHDDwwvakU8atRww== - dependencies: - "@atproto/lex-schema" "^0.0.16" - core-js "^3" - tslib "^2.8.1" - -"@atproto/lex-json@^0.0.13": - version "0.0.13" - resolved "https://registry.yarnpkg.com/@atproto/lex-json/-/lex-json-0.0.13.tgz#b0081f786aeeb1707087318fb03c928e75c19059" - integrity sha512-hwLhkKaIHulGJpt0EfXAEWdrxqM2L1tV/tvilzhMp3QxPqYgXchFnrfVmLsyFDx6P6qkH1GsX/XC2V36U0UlPQ== - dependencies: - "@atproto/lex-data" "^0.0.13" - tslib "^2.8.1" - -"@atproto/lex-json@^0.0.14": - version "0.0.14" - resolved "https://registry.yarnpkg.com/@atproto/lex-json/-/lex-json-0.0.14.tgz#717e533ab583aa5f580acb2a77d9aa3e7eddaa17" - integrity sha512-6lPkDKqe7teEu4WrN5q7400cvZKgYS3uwUMvzG3F9XkgVYhOwSDCtouV/nSLBbpvo3l9OP0kiigtclcNcyekww== - dependencies: - "@atproto/lex-data" "^0.0.14" - tslib "^2.8.1" - -"@atproto/lex-resolver@^0.0.19": - version "0.0.19" - resolved "https://registry.yarnpkg.com/@atproto/lex-resolver/-/lex-resolver-0.0.19.tgz#806fcb71e72d0db51e2eb29c594eddb8c4087414" - integrity sha512-oATn4RpZNLh5rp9doN5/UOYS/Cd25GOD90ohB5jnnmeoF8jTupqIYTVhntbnx1EFn+5tTlgkyXEBV+XESBUcdQ== - dependencies: - "@atproto-labs/did-resolver" "^0.2.6" - "@atproto/crypto" "^0.4.5" - "@atproto/lex-client" "^0.0.17" - "@atproto/lex-data" "^0.0.14" - "@atproto/lex-document" "^0.0.17" - "@atproto/lex-schema" "^0.0.16" - "@atproto/repo" "^0.8.13" - "@atproto/syntax" "^0.5.1" - tslib "^2.8.1" - -"@atproto/lex-schema@^0.0.14": - version "0.0.14" - resolved "https://registry.yarnpkg.com/@atproto/lex-schema/-/lex-schema-0.0.14.tgz#c29f5278d55441e76802d64f7a9df60e7a87786b" - integrity sha512-xUxFuXdgVVI1IBDXcQlanH7HuEo9Pk65DYifnhqFDzNRH9SZQxPvPO+rOxMG/bRHygPaI+A+UbXr+S7qpPYOLg== - dependencies: - "@atproto/lex-data" "^0.0.13" - "@atproto/syntax" "^0.5.0" - tslib "^2.8.1" - -"@atproto/lex-schema@^0.0.16": - version "0.0.16" - resolved "https://registry.yarnpkg.com/@atproto/lex-schema/-/lex-schema-0.0.16.tgz#8362932e239b7eaa7c5d6982d06c3147e3afd138" - integrity sha512-O+IorivZHJPeV3kU3NDD2yI8ATfckOphgvDfeiyKHRTxRUKS+lHMCpGUiSTC3fJrfMvYITrruUVViUHVEScrbA== - dependencies: - "@atproto/lex-data" "^0.0.14" - "@atproto/syntax" "^0.5.1" - "@standard-schema/spec" "^1.1.0" - iso-datestring-validator "^2.2.2" - tslib "^2.8.1" - -"@atproto/lexicon@^0.6.0", "@atproto/lexicon@^0.6.2": - version "0.6.2" - resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.6.2.tgz#f6152a2119df953236ca127c4b30e332265e81e7" - integrity sha512-p3Ly6hinVZW0ETuAXZMeUGwuMm3g8HvQMQ41yyEE6AL0hAkfeKFaZKos6BdBrr6CjkpbrDZqE8M+5+QOceysMw== - dependencies: - "@atproto/common-web" "^0.4.18" - "@atproto/syntax" "^0.5.0" - iso-datestring-validator "^2.2.2" - multiformats "^9.9.0" - zod "^3.23.8" - -"@atproto/oauth-provider-api@0.3.7": - version "0.3.7" - resolved "https://registry.yarnpkg.com/@atproto/oauth-provider-api/-/oauth-provider-api-0.3.7.tgz#7b911256536a72dbba6f38081a200836a3ab50b8" - integrity sha512-7yU9vuQFt/hy4NzlDtn+LuhIGvVKkhgWAkCmopnseMPBw6oGPT90uOsTxMkVGtHuKVvBSz7hOXoELXpnZq3gDQ== - dependencies: - "@atproto/jwk" "0.6.0" - "@atproto/oauth-types" "0.6.2" - -"@atproto/oauth-provider-frontend@0.2.9": - version "0.2.9" - resolved "https://registry.yarnpkg.com/@atproto/oauth-provider-frontend/-/oauth-provider-frontend-0.2.9.tgz#68486c0d9710a1325a02a303a6b22c733239b219" - integrity sha512-m1NhqC7ydEpF5ELV13CxkgCawyMX8wbAXls/4af3Tsz3IOkdM6zPgQeYK7Zj+7Nywjml4mZTkJ8rZiT6O9+5sw== - optionalDependencies: - "@atproto/oauth-provider-api" "0.3.7" - -"@atproto/oauth-provider-ui@0.4.3": - version "0.4.3" - resolved "https://registry.yarnpkg.com/@atproto/oauth-provider-ui/-/oauth-provider-ui-0.4.3.tgz#c80b076c2d2a7cc45e45866796f09b959bcf1c03" - integrity sha512-BLNZmtOwoHu2qk/Oi6dUR8TcXQaJre6wgW8YjkW5bKf+Vftn3PGzh8bKgr1fQZDYweZ6AF148imA2OzVSAaLHQ== - optionalDependencies: - "@atproto/oauth-provider-api" "0.3.7" - -"@atproto/oauth-provider@^0.15.14": - version "0.15.14" - resolved "https://registry.yarnpkg.com/@atproto/oauth-provider/-/oauth-provider-0.15.14.tgz#d969018b4ad5c0dd5863150cb8c1b65458738589" - integrity sha512-arA3O+Ye1YBhoIUnZtn8wfatnVnwiZrGyNkxhH0nqGbh/RRfwA5W0tgnSDq0VMclLkrPY/OnZ4v3oo9N81yWGg== - dependencies: - "@atproto-labs/fetch" "^0.2.3" - "@atproto-labs/fetch-node" "^0.2.0" - "@atproto-labs/pipe" "^0.1.1" - "@atproto-labs/simple-store" "^0.3.0" - "@atproto-labs/simple-store-memory" "^0.1.4" - "@atproto/common" "^0.5.15" - "@atproto/did" "^0.3.0" - "@atproto/jwk" "^0.6.0" - "@atproto/jwk-jose" "^0.1.11" - "@atproto/lex-document" "^0.0.17" - "@atproto/lex-resolver" "^0.0.19" - "@atproto/oauth-provider-api" "0.3.7" - "@atproto/oauth-provider-frontend" "0.2.9" - "@atproto/oauth-provider-ui" "0.4.3" - "@atproto/oauth-scopes" "^0.3.2" - "@atproto/oauth-types" "^0.6.3" - "@atproto/syntax" "^0.5.1" - "@hapi/accept" "^6.0.3" - "@hapi/address" "^5.1.1" - "@hapi/bourne" "^3.0.0" - "@hapi/content" "^6.0.0" - cookie "^0.7.0" - disposable-email-domains-js "^1.5.0" - forwarded "^0.2.0" - http-errors "^2.0.0" - ioredis "^5.3.2" - jose "^5.2.0" - zod "^3.23.8" - -"@atproto/oauth-scopes@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@atproto/oauth-scopes/-/oauth-scopes-0.3.2.tgz#26443492936d6da05deadeaf1223bd10adc45a33" - integrity sha512-88awgvB9asSR86KjEXL31N9nFd1XG+QtbzEJFw5/wwEv41tS4S8c4Prqb/MAzVuFQ4OHOndYo1VDD99vGMpqTA== - dependencies: - "@atproto/did" "^0.3.0" - "@atproto/syntax" "^0.5.0" - -"@atproto/oauth-types@0.6.2": - version "0.6.2" - resolved "https://registry.yarnpkg.com/@atproto/oauth-types/-/oauth-types-0.6.2.tgz#d829fae63421dcea7ac703e84460175d2f8d9299" - integrity sha512-2cuboM4RQBCYR8NQC5uGRkW6KgCgKyq/B5/+tnMmWZYtZGVUQvsUWQHK/ZiMCnVXbcDNtc/RIEJQJDZ8FXMoxg== - dependencies: - "@atproto/did" "0.3.0" - "@atproto/jwk" "0.6.0" - zod "^3.23.8" - -"@atproto/oauth-types@^0.6.3": - version "0.6.3" - resolved "https://registry.yarnpkg.com/@atproto/oauth-types/-/oauth-types-0.6.3.tgz#4fc996d0af61874830079d1b1bddc7c0774ad6b2" - integrity sha512-jdKuoPknJuh/WjI+mYk7agSbx9mNVMbS6Dr3k1z2YMY2oRiCQjxYBuo4MLKATbxj05nMQaZRWlHRUazoAu5Cng== - dependencies: - "@atproto/did" "^0.3.0" - "@atproto/jwk" "^0.6.0" - zod "^3.23.8" - -"@atproto/ozone@^0.1.167": - version "0.1.167" - resolved "https://registry.yarnpkg.com/@atproto/ozone/-/ozone-0.1.167.tgz#c3974bbe06f0926f165f90d5ba51ba9ce2032fff" - integrity sha512-AFquyND8zsskjkDc3WrQObUnZlEky05pFo0YYLy5JoqQN0WXIePLgGY0SC0EIokfF8iYXJE1ZM1u/dgA7DHqGQ== - dependencies: - "@atproto/api" "^0.19.4" - "@atproto/common" "^0.5.14" - "@atproto/crypto" "^0.4.5" - "@atproto/identity" "^0.4.12" - "@atproto/lexicon" "^0.6.2" - "@atproto/syntax" "^0.5.1" - "@atproto/ws-client" "^0.0.4" - "@atproto/xrpc" "^0.7.7" - "@atproto/xrpc-server" "^0.10.16" - "@did-plc/lib" "^0.0.1" - compression "^1.7.4" - cors "^2.8.5" - express "^4.17.2" - http-terminator "^3.2.0" - kysely "^0.22.0" - lande "^1.0.10" - multiformats "^9.9.0" - p-queue "^6.6.2" - pg "^8.10.0" - pino-http "^8.2.1" - structured-headers "^1.0.1" - typed-emitter "^2.1.0" - uint8arrays "3.0.0" - undici "^6.14.1" - ws "^8.12.0" - -"@atproto/pds@^0.4.216": - version "0.4.216" - resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.4.216.tgz#4d9c73a529bd00893753aba1e7bb33f99b9aaaf4" - integrity sha512-yPNatCb2kvudRp5DMbPemN1+uMsLOJDydw2PCBMxuzdimOf10PsekOhhZxtfGGRvk2NbvKoyfUvkROoFmTr+ew== - dependencies: - "@atproto-labs/fetch-node" "^0.2.0" - "@atproto-labs/simple-store" "^0.3.0" - "@atproto-labs/simple-store-memory" "^0.1.4" - "@atproto-labs/simple-store-redis" "^0.0.1" - "@atproto-labs/xrpc-utils" "^0.0.24" - "@atproto/api" "^0.19.4" - "@atproto/aws" "^0.2.31" - "@atproto/common" "^0.5.15" - "@atproto/crypto" "^0.4.5" - "@atproto/identity" "^0.4.12" - "@atproto/lex-cbor" "^0.0.15" - "@atproto/lex-data" "^0.0.14" - "@atproto/lexicon" "^0.6.2" - "@atproto/oauth-provider" "^0.15.14" - "@atproto/oauth-scopes" "^0.3.2" - "@atproto/repo" "^0.8.13" - "@atproto/syntax" "^0.5.1" - "@atproto/xrpc" "^0.7.7" - "@atproto/xrpc-server" "^0.10.17" - "@did-plc/lib" "^0.0.4" - "@hapi/address" "^5.1.1" - better-sqlite3 "^10.0.0" - bytes "^3.1.2" - compression "^1.7.4" - cors "^2.8.5" - disposable-email-domains-js "^1.5.0" - express "^4.17.2" - express-async-errors "^3.1.1" - file-type "^16.5.4" - glob "^10.3.10" - handlebars "^4.7.7" - http-terminator "^3.2.0" - ioredis "^5.3.2" - jose "^5.0.1" - key-encoder "^2.0.3" - kysely "^0.22.0" - multiformats "^9.9.0" - nodemailer "^6.8.0" - nodemailer-html-to-text "^3.2.0" - p-queue "^6.6.2" - pino "^8.21.0" - pino-http "^8.2.1" - typed-emitter "^2.1.0" - uint8arrays "3.0.0" - undici "^6.19.8" - zod "^3.23.8" - -"@atproto/repo@^0.8.11", "@atproto/repo@^0.8.12": - version "0.8.12" - resolved "https://registry.yarnpkg.com/@atproto/repo/-/repo-0.8.12.tgz#f1caee2ff954a917f921569eac22ab669b39a0af" - integrity sha512-QpVTVulgfz5PUiCTELlDBiRvnsnwrFWi+6CfY88VwXzrRHd9NE8GItK7sfxQ6U65vD/idH8ddCgFrlrsn1REPQ== - dependencies: - "@atproto/common" "^0.5.3" - "@atproto/common-web" "^0.4.7" - "@atproto/crypto" "^0.4.5" - "@atproto/lexicon" "^0.6.0" - "@ipld/dag-cbor" "^7.0.0" - multiformats "^9.9.0" - uint8arrays "3.0.0" - varint "^6.0.0" - zod "^3.23.8" - -"@atproto/repo@^0.8.13": - version "0.8.13" - resolved "https://registry.yarnpkg.com/@atproto/repo/-/repo-0.8.13.tgz#70160b8b3f78b6addcba7cf3e3ae06306e6b6641" - integrity sha512-VS8XHaBMGdq60xwRI5zQmXzsMF1hU7NKPjmkdr65tJdrv2z0VW77mG01Ui19Xh9O0mUc/LG6GEhwVrabB9Txow== - dependencies: - "@atproto/common" "^0.5.14" - "@atproto/common-web" "^0.4.18" - "@atproto/crypto" "^0.4.5" - "@atproto/lexicon" "^0.6.2" - "@ipld/dag-cbor" "^7.0.0" - multiformats "^9.9.0" - uint8arrays "3.0.0" - varint "^6.0.0" - zod "^3.23.8" - -"@atproto/sync@^0.1.40": - version "0.1.40" - resolved "https://registry.yarnpkg.com/@atproto/sync/-/sync-0.1.40.tgz#b8b467ac4fbf2e682d36cd5697508f993e9e645a" - integrity sha512-tnzFPqKBXPXpuGvx87sjqLHgEzIOT/QfQZUp0YOUHtpipBgvijEnNXV9XeTIgiUAv69wyRR+6YjJkLCYfHpVwQ== - dependencies: - "@atproto/common" "^0.5.14" - "@atproto/identity" "^0.4.12" - "@atproto/lexicon" "^0.6.2" - "@atproto/repo" "^0.8.12" - "@atproto/syntax" "^0.5.0" - "@atproto/xrpc-server" "^0.10.15" - multiformats "^9.9.0" - p-queue "^6.6.2" - ws "^8.12.0" - -"@atproto/syntax@^0.5.0": - version "0.5.0" - resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.5.0.tgz#061ef538aee784f8e5fa1ea50a7f5beb4c276c9b" - integrity sha512-UA2DSpGdOQzUQ4gi5SH+NEJz/YR3a3Fg3y2oh+xETDSiTRmA4VhHRCojhXAVsBxUT6EnItw190C/KN+DWW90kw== - dependencies: - tslib "^2.8.1" - -"@atproto/syntax@^0.5.1": - version "0.5.1" - resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.5.1.tgz#78257b903a0723720dca32110379208791ac3c24" - integrity sha512-J8DJjgKgACIyCTbpfvoTnf7+ofTx1kxTGO7KAftkC+jczaMdQhKdgIBAg2DaYy+80cvYGTHy5q/HI9qMAwGbWw== - dependencies: - tslib "^2.8.1" - -"@atproto/ws-client@^0.0.4": - version "0.0.4" - resolved "https://registry.yarnpkg.com/@atproto/ws-client/-/ws-client-0.0.4.tgz#9e436c0e72abea5da0d5a7e8ec862cec0fdb10cd" - integrity sha512-dox1XIymuC7/ZRhUqKezIGgooZS45C6vHCfu0PnWjfvsLCK2kAlnvX4IBkA/WpcoijDhQ9ejChnFbo/sLmgvAg== - dependencies: - "@atproto/common" "^0.5.3" - ws "^8.12.0" - -"@atproto/xrpc-server@^0.10.0", "@atproto/xrpc-server@^0.10.15": - version "0.10.15" - resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.10.15.tgz#c767437fdf7d64ec6cbc9c9cf97a27d2b62554b8" - integrity sha512-ryGVAKuLU0Nqkv25gsPzffJhxnCXwPOyBi+sNAfP7n+mDDwcumH6RWySEHoDDrTsGvAP2r8o2ZrLCWuzKm7vSg== - dependencies: - "@atproto/common" "^0.5.14" - "@atproto/crypto" "^0.4.5" - "@atproto/lex-cbor" "^0.0.14" - "@atproto/lex-client" "^0.0.15" - "@atproto/lex-data" "^0.0.13" - "@atproto/lex-json" "^0.0.13" - "@atproto/lex-schema" "^0.0.14" - "@atproto/lexicon" "^0.6.2" - "@atproto/ws-client" "^0.0.4" - "@atproto/xrpc" "^0.7.7" - express "^4.17.2" - http-errors "^2.0.0" - mime-types "^2.1.35" - rate-limiter-flexible "^2.4.1" - ws "^8.12.0" - -"@atproto/xrpc-server@^0.10.16", "@atproto/xrpc-server@^0.10.17": - version "0.10.17" - resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.10.17.tgz#1016d4a6d97966a3f80e8785e5d5cba7b68c1c60" - integrity sha512-FjexO6P/LRTx6/FdiWTzycFF4TgACW9npsFOitnocydTCOLYouP0OvUwdkxkreFC7qerT4+ARKpqrxRzyj0MNA== - dependencies: - "@atproto/common" "^0.5.15" - "@atproto/crypto" "^0.4.5" - "@atproto/lex-cbor" "^0.0.15" - "@atproto/lex-client" "^0.0.17" - "@atproto/lex-data" "^0.0.14" - "@atproto/lex-json" "^0.0.14" - "@atproto/lex-schema" "^0.0.16" - "@atproto/lexicon" "^0.6.2" - "@atproto/ws-client" "^0.0.4" - "@atproto/xrpc" "^0.7.7" - express "^4.17.2" - http-errors "^2.0.0" - mime-types "^2.1.35" - rate-limiter-flexible "^2.4.1" - ws "^8.12.0" - -"@atproto/xrpc@^0.7.6", "@atproto/xrpc@^0.7.7": - version "0.7.7" - resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.7.7.tgz#c0e3106c854cb9bc7d3129de2f31b8256eb0ed11" - integrity sha512-K1ZyO/BU8JNtXX5dmPp7b5UrkLMMqpsIa/Lrj5D3Su+j1Xwq1m6QJ2XJ1AgjEjkI1v4Muzm7klianLE6XGxtmA== - dependencies: - "@atproto/lexicon" "^0.6.0" - zod "^3.23.8" - -"@aws-crypto/crc32@5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/crc32/-/crc32-5.2.0.tgz#cfcc22570949c98c6689cfcbd2d693d36cdae2e1" - integrity sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg== - dependencies: - "@aws-crypto/util" "^5.2.0" - "@aws-sdk/types" "^3.222.0" - tslib "^2.6.2" - -"@aws-crypto/crc32c@5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz#4e34aab7f419307821509a98b9b08e84e0c1917e" - integrity sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag== - dependencies: - "@aws-crypto/util" "^5.2.0" - "@aws-sdk/types" "^3.222.0" - tslib "^2.6.2" - -"@aws-crypto/sha1-browser@5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz#b0ee2d2821d3861f017e965ef3b4cb38e3b6a0f4" - integrity sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg== - dependencies: - "@aws-crypto/supports-web-crypto" "^5.2.0" - "@aws-crypto/util" "^5.2.0" - "@aws-sdk/types" "^3.222.0" - "@aws-sdk/util-locate-window" "^3.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.6.2" - -"@aws-crypto/sha256-browser@5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz#153895ef1dba6f9fce38af550e0ef58988eb649e" - integrity sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw== - dependencies: - "@aws-crypto/sha256-js" "^5.2.0" - "@aws-crypto/supports-web-crypto" "^5.2.0" - "@aws-crypto/util" "^5.2.0" - "@aws-sdk/types" "^3.222.0" - "@aws-sdk/util-locate-window" "^3.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.6.2" - -"@aws-crypto/sha256-js@5.2.0", "@aws-crypto/sha256-js@^5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz#c4fdb773fdbed9a664fc1a95724e206cf3860042" - integrity sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA== - dependencies: - "@aws-crypto/util" "^5.2.0" - "@aws-sdk/types" "^3.222.0" - tslib "^2.6.2" - -"@aws-crypto/supports-web-crypto@^5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz#a1e399af29269be08e695109aa15da0a07b5b5fb" - integrity sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg== - dependencies: - tslib "^2.6.2" - -"@aws-crypto/util@5.2.0", "@aws-crypto/util@^5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/util/-/util-5.2.0.tgz#71284c9cffe7927ddadac793c14f14886d3876da" - integrity sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ== - dependencies: - "@aws-sdk/types" "^3.222.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.6.2" - -"@aws-sdk/client-cloudfront@^3.879.0": - version "3.1002.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-cloudfront/-/client-cloudfront-3.1002.0.tgz#e9142dd17a57f1da28fa7425d5c5d7e7d6166c14" - integrity sha512-1JRFy+1K59FzjEl3IzYw6bVB25to0VokK99Gg57Apu7WSBKvD7KLc2CtTiSUYPQS2Q6M+7lHnh3cj+8IFbt4/Q== - dependencies: - "@aws-crypto/sha256-browser" "5.2.0" - "@aws-crypto/sha256-js" "5.2.0" - "@aws-sdk/core" "^3.973.17" - "@aws-sdk/credential-provider-node" "^3.972.16" - "@aws-sdk/middleware-host-header" "^3.972.6" - "@aws-sdk/middleware-logger" "^3.972.6" - "@aws-sdk/middleware-recursion-detection" "^3.972.6" - "@aws-sdk/middleware-user-agent" "^3.972.17" - "@aws-sdk/region-config-resolver" "^3.972.6" - "@aws-sdk/types" "^3.973.4" - "@aws-sdk/util-endpoints" "^3.996.3" - "@aws-sdk/util-user-agent-browser" "^3.972.6" - "@aws-sdk/util-user-agent-node" "^3.973.2" - "@smithy/config-resolver" "^4.4.9" - "@smithy/core" "^3.23.7" - "@smithy/fetch-http-handler" "^5.3.12" - "@smithy/hash-node" "^4.2.10" - "@smithy/invalid-dependency" "^4.2.10" - "@smithy/middleware-content-length" "^4.2.10" - "@smithy/middleware-endpoint" "^4.4.21" - "@smithy/middleware-retry" "^4.4.38" - "@smithy/middleware-serde" "^4.2.11" - "@smithy/middleware-stack" "^4.2.10" - "@smithy/node-config-provider" "^4.3.10" - "@smithy/node-http-handler" "^4.4.13" - "@smithy/protocol-http" "^5.3.10" - "@smithy/smithy-client" "^4.12.1" - "@smithy/types" "^4.13.0" - "@smithy/url-parser" "^4.2.10" - "@smithy/util-base64" "^4.3.1" - "@smithy/util-body-length-browser" "^4.2.1" - "@smithy/util-body-length-node" "^4.2.2" - "@smithy/util-defaults-mode-browser" "^4.3.37" - "@smithy/util-defaults-mode-node" "^4.2.40" - "@smithy/util-endpoints" "^3.3.1" - "@smithy/util-middleware" "^4.2.10" - "@smithy/util-retry" "^4.2.10" - "@smithy/util-stream" "^4.5.16" - "@smithy/util-utf8" "^4.2.1" - "@smithy/util-waiter" "^4.2.10" - tslib "^2.6.2" - -"@aws-sdk/client-kms@^3.879.0": - version "3.1002.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-kms/-/client-kms-3.1002.0.tgz#1f46196babcdd9fdc30e008be9ccabf649ea0a2a" - integrity sha512-J0UN301gkKT95ANzIRCDiEaoJkVi22uBBpwv6wTgyMn7BaS2ISUWe01itZ5wQdS541V8NIiyJEjc9hlYWBHb5Q== - dependencies: - "@aws-crypto/sha256-browser" "5.2.0" - "@aws-crypto/sha256-js" "5.2.0" - "@aws-sdk/core" "^3.973.17" - "@aws-sdk/credential-provider-node" "^3.972.16" - "@aws-sdk/middleware-host-header" "^3.972.6" - "@aws-sdk/middleware-logger" "^3.972.6" - "@aws-sdk/middleware-recursion-detection" "^3.972.6" - "@aws-sdk/middleware-user-agent" "^3.972.17" - "@aws-sdk/region-config-resolver" "^3.972.6" - "@aws-sdk/types" "^3.973.4" - "@aws-sdk/util-endpoints" "^3.996.3" - "@aws-sdk/util-user-agent-browser" "^3.972.6" - "@aws-sdk/util-user-agent-node" "^3.973.2" - "@smithy/config-resolver" "^4.4.9" - "@smithy/core" "^3.23.7" - "@smithy/fetch-http-handler" "^5.3.12" - "@smithy/hash-node" "^4.2.10" - "@smithy/invalid-dependency" "^4.2.10" - "@smithy/middleware-content-length" "^4.2.10" - "@smithy/middleware-endpoint" "^4.4.21" - "@smithy/middleware-retry" "^4.4.38" - "@smithy/middleware-serde" "^4.2.11" - "@smithy/middleware-stack" "^4.2.10" - "@smithy/node-config-provider" "^4.3.10" - "@smithy/node-http-handler" "^4.4.13" - "@smithy/protocol-http" "^5.3.10" - "@smithy/smithy-client" "^4.12.1" - "@smithy/types" "^4.13.0" - "@smithy/url-parser" "^4.2.10" - "@smithy/util-base64" "^4.3.1" - "@smithy/util-body-length-browser" "^4.2.1" - "@smithy/util-body-length-node" "^4.2.2" - "@smithy/util-defaults-mode-browser" "^4.3.37" - "@smithy/util-defaults-mode-node" "^4.2.40" - "@smithy/util-endpoints" "^3.3.1" - "@smithy/util-middleware" "^4.2.10" - "@smithy/util-retry" "^4.2.10" - "@smithy/util-utf8" "^4.2.1" - tslib "^2.6.2" - -"@aws-sdk/client-s3@^3.879.0": - version "3.1002.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.1002.0.tgz#f84d4780d0a1858fbb81cc1199601967b6d08f0a" - integrity sha512-tc+vZgvjcm+1Ot+YhQjXZxVELKGGGO3D5cuR4p5xaeitXYX2+RRiz4/WdSak9slumIClnlXsdqhJ0OHognUT+w== - dependencies: - "@aws-crypto/sha1-browser" "5.2.0" - "@aws-crypto/sha256-browser" "5.2.0" - "@aws-crypto/sha256-js" "5.2.0" - "@aws-sdk/core" "^3.973.17" - "@aws-sdk/credential-provider-node" "^3.972.16" - "@aws-sdk/middleware-bucket-endpoint" "^3.972.6" - "@aws-sdk/middleware-expect-continue" "^3.972.6" - "@aws-sdk/middleware-flexible-checksums" "^3.973.3" - "@aws-sdk/middleware-host-header" "^3.972.6" - "@aws-sdk/middleware-location-constraint" "^3.972.6" - "@aws-sdk/middleware-logger" "^3.972.6" - "@aws-sdk/middleware-recursion-detection" "^3.972.6" - "@aws-sdk/middleware-sdk-s3" "^3.972.17" - "@aws-sdk/middleware-ssec" "^3.972.6" - "@aws-sdk/middleware-user-agent" "^3.972.17" - "@aws-sdk/region-config-resolver" "^3.972.6" - "@aws-sdk/signature-v4-multi-region" "^3.996.5" - "@aws-sdk/types" "^3.973.4" - "@aws-sdk/util-endpoints" "^3.996.3" - "@aws-sdk/util-user-agent-browser" "^3.972.6" - "@aws-sdk/util-user-agent-node" "^3.973.2" - "@smithy/config-resolver" "^4.4.9" - "@smithy/core" "^3.23.7" - "@smithy/eventstream-serde-browser" "^4.2.10" - "@smithy/eventstream-serde-config-resolver" "^4.3.10" - "@smithy/eventstream-serde-node" "^4.2.10" - "@smithy/fetch-http-handler" "^5.3.12" - "@smithy/hash-blob-browser" "^4.2.11" - "@smithy/hash-node" "^4.2.10" - "@smithy/hash-stream-node" "^4.2.10" - "@smithy/invalid-dependency" "^4.2.10" - "@smithy/md5-js" "^4.2.10" - "@smithy/middleware-content-length" "^4.2.10" - "@smithy/middleware-endpoint" "^4.4.21" - "@smithy/middleware-retry" "^4.4.38" - "@smithy/middleware-serde" "^4.2.11" - "@smithy/middleware-stack" "^4.2.10" - "@smithy/node-config-provider" "^4.3.10" - "@smithy/node-http-handler" "^4.4.13" - "@smithy/protocol-http" "^5.3.10" - "@smithy/smithy-client" "^4.12.1" - "@smithy/types" "^4.13.0" - "@smithy/url-parser" "^4.2.10" - "@smithy/util-base64" "^4.3.1" - "@smithy/util-body-length-browser" "^4.2.1" - "@smithy/util-body-length-node" "^4.2.2" - "@smithy/util-defaults-mode-browser" "^4.3.37" - "@smithy/util-defaults-mode-node" "^4.2.40" - "@smithy/util-endpoints" "^3.3.1" - "@smithy/util-middleware" "^4.2.10" - "@smithy/util-retry" "^4.2.10" - "@smithy/util-stream" "^4.5.16" - "@smithy/util-utf8" "^4.2.1" - "@smithy/util-waiter" "^4.2.10" - tslib "^2.6.2" - -"@aws-sdk/core@^3.973.17": - version "3.973.17" - resolved "https://registry.yarnpkg.com/@aws-sdk/core/-/core-3.973.17.tgz#d46f3865398352017e78e0d794397fec8861289b" - integrity sha512-VtgGP0TjbCeyp6DQpiBqJKbemTSIaN2bZc3UbeTDCani3lBCyxn75ouJYD6koSSp0bh7rKLEbUpiFsNCI7tr0w== - dependencies: - "@aws-sdk/types" "^3.973.4" - "@aws-sdk/xml-builder" "^3.972.9" - "@smithy/core" "^3.23.7" - "@smithy/node-config-provider" "^4.3.10" - "@smithy/property-provider" "^4.2.10" - "@smithy/protocol-http" "^5.3.10" - "@smithy/signature-v4" "^5.3.10" - "@smithy/smithy-client" "^4.12.1" - "@smithy/types" "^4.13.0" - "@smithy/util-base64" "^4.3.1" - "@smithy/util-middleware" "^4.2.10" - "@smithy/util-utf8" "^4.2.1" - tslib "^2.6.2" - -"@aws-sdk/crc64-nvme@^3.972.3": - version "3.972.3" - resolved "https://registry.yarnpkg.com/@aws-sdk/crc64-nvme/-/crc64-nvme-3.972.3.tgz#cb005d9d4b53e0ad6a9e2f2ddf153d9f1df12da8" - integrity sha512-UExeK+EFiq5LAcbHm96CQLSia+5pvpUVSAsVApscBzayb7/6dJBJKwV4/onsk4VbWSmqxDMcfuTD+pC4RxgZHg== - dependencies: - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@aws-sdk/credential-provider-env@^3.972.15": - version "3.972.15" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.15.tgz#9fea1e8a39f678beff0912dbaa1e5653239fe011" - integrity sha512-RhHQG1lhkWHL4tK1C/KDjaOeis+9U0tAMnWDiwiSVQZMC7CsST9Xin+sK89XywJ5g/tyABtb7TvFePJ4Te5XSQ== - dependencies: - "@aws-sdk/core" "^3.973.17" - "@aws-sdk/types" "^3.973.4" - "@smithy/property-provider" "^4.2.10" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@aws-sdk/credential-provider-http@^3.972.17": - version "3.972.17" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.17.tgz#e9c881fbd5758d31f5e82ff8657099923c1bbdec" - integrity sha512-b/bDL76p51+yQ+0O9ZDH5nw/ioE0sRYkjwjOwFWAWZXo6it2kQZUOXhVpjohx3ldKyUxt/SwAivjUu1Nr/PWlQ== - dependencies: - "@aws-sdk/core" "^3.973.17" - "@aws-sdk/types" "^3.973.4" - "@smithy/fetch-http-handler" "^5.3.12" - "@smithy/node-http-handler" "^4.4.13" - "@smithy/property-provider" "^4.2.10" - "@smithy/protocol-http" "^5.3.10" - "@smithy/smithy-client" "^4.12.1" - "@smithy/types" "^4.13.0" - "@smithy/util-stream" "^4.5.16" - tslib "^2.6.2" - -"@aws-sdk/credential-provider-ini@^3.972.15": - version "3.972.15" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.15.tgz#8562fe36c35334fd454307ebfabc50f2f016b10b" - integrity sha512-qWnM+wB8MmU2kKY7f4KowKjOjkwRosaFxrtseEEIefwoXn1SjN+CbHzXBVdTAQxxkbBiqhPgJ/WHiPtES4grRQ== - dependencies: - "@aws-sdk/core" "^3.973.17" - "@aws-sdk/credential-provider-env" "^3.972.15" - "@aws-sdk/credential-provider-http" "^3.972.17" - "@aws-sdk/credential-provider-login" "^3.972.15" - "@aws-sdk/credential-provider-process" "^3.972.15" - "@aws-sdk/credential-provider-sso" "^3.972.15" - "@aws-sdk/credential-provider-web-identity" "^3.972.15" - "@aws-sdk/nested-clients" "^3.996.5" - "@aws-sdk/types" "^3.973.4" - "@smithy/credential-provider-imds" "^4.2.10" - "@smithy/property-provider" "^4.2.10" - "@smithy/shared-ini-file-loader" "^4.4.5" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@aws-sdk/credential-provider-login@^3.972.15": - version "3.972.15" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.15.tgz#6c64c142d75614e2f5c35ebea05d9ce3994b3e28" - integrity sha512-x92FJy34/95wgu+qOGD8SHcgh1hZ9Qx2uFtQEGn4m9Ljou8ICIv3Ybq5yxdB7A60S8ZGCQB0mIopmjJwiLbh5g== - dependencies: - "@aws-sdk/core" "^3.973.17" - "@aws-sdk/nested-clients" "^3.996.5" - "@aws-sdk/types" "^3.973.4" - "@smithy/property-provider" "^4.2.10" - "@smithy/protocol-http" "^5.3.10" - "@smithy/shared-ini-file-loader" "^4.4.5" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@aws-sdk/credential-provider-node@^3.972.16": - version "3.972.16" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.16.tgz#58bf657b7227f62008e1bc6c73b793edaff2a1a9" - integrity sha512-7mlt14Ee4rPFAFUVgpWE7+0CBhetJJyzVFqfIsMp7sgyOSm9Y/+qHZOWAuK5I4JNc+Y5PltvJ9kssTzRo92iXQ== - dependencies: - "@aws-sdk/credential-provider-env" "^3.972.15" - "@aws-sdk/credential-provider-http" "^3.972.17" - "@aws-sdk/credential-provider-ini" "^3.972.15" - "@aws-sdk/credential-provider-process" "^3.972.15" - "@aws-sdk/credential-provider-sso" "^3.972.15" - "@aws-sdk/credential-provider-web-identity" "^3.972.15" - "@aws-sdk/types" "^3.973.4" - "@smithy/credential-provider-imds" "^4.2.10" - "@smithy/property-provider" "^4.2.10" - "@smithy/shared-ini-file-loader" "^4.4.5" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@aws-sdk/credential-provider-process@^3.972.15": - version "3.972.15" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.15.tgz#2d5498024a9cc2c77658ee4850a383156623c5ff" - integrity sha512-PrH3iTeD18y/8uJvQD2s/T87BTGhsdS/1KZU7ReWHXsplBwvCqi7AbnnNbML1pFlQwRWCE2RdSZFWDVId3CvkA== - dependencies: - "@aws-sdk/core" "^3.973.17" - "@aws-sdk/types" "^3.973.4" - "@smithy/property-provider" "^4.2.10" - "@smithy/shared-ini-file-loader" "^4.4.5" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@aws-sdk/credential-provider-sso@^3.972.15": - version "3.972.15" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.15.tgz#c740bc0b91507f5593078d97f1d0521e32c1fc44" - integrity sha512-M/+LBHTPKZxxXckM6m4dnJeR+jlm9NynH9b2YDswN4Zj2St05SK/crdL3Wy3WfJTZootnnhm3oTh87Usl7PS7w== - dependencies: - "@aws-sdk/core" "^3.973.17" - "@aws-sdk/nested-clients" "^3.996.5" - "@aws-sdk/token-providers" "3.1002.0" - "@aws-sdk/types" "^3.973.4" - "@smithy/property-provider" "^4.2.10" - "@smithy/shared-ini-file-loader" "^4.4.5" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@aws-sdk/credential-provider-web-identity@^3.972.15": - version "3.972.15" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.15.tgz#937e0e10f566d6ce71ff7170602a60eabf82d93f" - integrity sha512-QTH6k93v+UOfFam/ado8zc71tH+enTVyuvLy9uEWXX1x894dN5ovtf/MdBDgFwq3g6c9mbtgVJ4B+yBqDtXvdA== - dependencies: - "@aws-sdk/core" "^3.973.17" - "@aws-sdk/nested-clients" "^3.996.5" - "@aws-sdk/types" "^3.973.4" - "@smithy/property-provider" "^4.2.10" - "@smithy/shared-ini-file-loader" "^4.4.5" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@aws-sdk/lib-storage@3.879.0": - version "3.879.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/lib-storage/-/lib-storage-3.879.0.tgz#62c8b6ad53e0d8e595d5ba486def140de413b0d1" - integrity sha512-FAb3vOfLIrf8lPuDoxKRu18DxXfQLEFm7MoXi0jd8ooFjD09jpVCQGNrRuMCqc688wrx7zJSovWObtn4LRjvrg== - dependencies: - "@smithy/abort-controller" "^4.0.5" - "@smithy/middleware-endpoint" "^4.1.19" - "@smithy/smithy-client" "^4.5.0" - buffer "5.6.0" - events "3.3.0" - stream-browserify "3.0.0" - tslib "^2.6.2" - -"@aws-sdk/middleware-bucket-endpoint@^3.972.6": - version "3.972.6" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.972.6.tgz#978d9e075450856b0bd93b2473b27cb17da3ce8a" - integrity sha512-3H2bhvb7Cb/S6WFsBy/Dy9q2aegC9JmGH1inO8Lb2sWirSqpLJlZmvQHPE29h2tIxzv6el/14X/tLCQ8BQU6ZQ== - dependencies: - "@aws-sdk/types" "^3.973.4" - "@aws-sdk/util-arn-parser" "^3.972.2" - "@smithy/node-config-provider" "^4.3.10" - "@smithy/protocol-http" "^5.3.10" - "@smithy/types" "^4.13.0" - "@smithy/util-config-provider" "^4.2.1" - tslib "^2.6.2" - -"@aws-sdk/middleware-expect-continue@^3.972.6": - version "3.972.6" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.972.6.tgz#72b3975fcc95b40df6bd8e6943c82d834ab92433" - integrity sha512-QMdffpU+GkSGC+bz6WdqlclqIeCsOfgX8JFZ5xvwDtX+UTj4mIXm3uXu7Ko6dBseRcJz1FA6T9OmlAAY6JgJUg== - dependencies: - "@aws-sdk/types" "^3.973.4" - "@smithy/protocol-http" "^5.3.10" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@aws-sdk/middleware-flexible-checksums@^3.973.3": - version "3.973.3" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.973.3.tgz#8363afc1e5eb25a87872451595eaeaf2495b0b4f" - integrity sha512-C9Mu9pXMpQh7jBydx0MrfQxNIKwJvKbVbJJ0GZthM+cQ+KTChXA01MwttRsMq0ZRb4pBJZQtIKDUxXusDr5OKg== - dependencies: - "@aws-crypto/crc32" "5.2.0" - "@aws-crypto/crc32c" "5.2.0" - "@aws-crypto/util" "5.2.0" - "@aws-sdk/core" "^3.973.17" - "@aws-sdk/crc64-nvme" "^3.972.3" - "@aws-sdk/types" "^3.973.4" - "@smithy/is-array-buffer" "^4.2.1" - "@smithy/node-config-provider" "^4.3.10" - "@smithy/protocol-http" "^5.3.10" - "@smithy/types" "^4.13.0" - "@smithy/util-middleware" "^4.2.10" - "@smithy/util-stream" "^4.5.16" - "@smithy/util-utf8" "^4.2.1" - tslib "^2.6.2" - -"@aws-sdk/middleware-host-header@^3.972.6": - version "3.972.6" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.6.tgz#810928d279e60cafecd0d6c70064af61f977cb56" - integrity sha512-5XHwjPH1lHB+1q4bfC7T8Z5zZrZXfaLcjSMwTd1HPSPrCmPFMbg3UQ5vgNWcVj0xoX4HWqTGkSf2byrjlnRg5w== - dependencies: - "@aws-sdk/types" "^3.973.4" - "@smithy/protocol-http" "^5.3.10" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@aws-sdk/middleware-location-constraint@^3.972.6": - version "3.972.6" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.972.6.tgz#d524d2b2b294a0c92d11ed2a52274ff0014e0384" - integrity sha512-XdZ2TLwyj3Am6kvUc67vquQvs6+D8npXvXgyEUJAdkUDx5oMFJKOqpK+UpJhVDsEL068WAJl2NEGzbSik7dGJQ== - dependencies: - "@aws-sdk/types" "^3.973.4" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@aws-sdk/middleware-logger@^3.972.6": - version "3.972.6" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.972.6.tgz#fcc9622ce4e29533a0c9e9d30031ff6a8ff37f60" - integrity sha512-iFnaMFMQdljAPrvsCVKYltPt2j40LQqukAbXvW7v0aL5I+1GO7bZ/W8m12WxW3gwyK5p5u1WlHg8TSAizC5cZw== - dependencies: - "@aws-sdk/types" "^3.973.4" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@aws-sdk/middleware-recursion-detection@^3.972.6": - version "3.972.6" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.6.tgz#35f02c25aea55d66365dc64dbfe8c5a5b9104756" - integrity sha512-dY4v3of5EEMvik6+UDwQ96KfUFDk8m1oZDdkSc5lwi4o7rFrjnv0A+yTV+gu230iybQZnKgDLg/rt2P3H+Vscw== - dependencies: - "@aws-sdk/types" "^3.973.4" - "@aws/lambda-invoke-store" "^0.2.2" - "@smithy/protocol-http" "^5.3.10" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@aws-sdk/middleware-sdk-s3@^3.972.17": - version "3.972.17" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.17.tgz#ac5f29dae6c99cb912e492c09a78ecff2a9b33c3" - integrity sha512-uSyOGoVFMP44pTt29MIMfsOjegqE/7lT0K3HG0GWPiH2lD4rqZC/TRi/kH4zrGiOQdsaLc+dkfd7Sb2q8vh+gA== - dependencies: - "@aws-sdk/core" "^3.973.17" - "@aws-sdk/types" "^3.973.4" - "@aws-sdk/util-arn-parser" "^3.972.2" - "@smithy/core" "^3.23.7" - "@smithy/node-config-provider" "^4.3.10" - "@smithy/protocol-http" "^5.3.10" - "@smithy/signature-v4" "^5.3.10" - "@smithy/smithy-client" "^4.12.1" - "@smithy/types" "^4.13.0" - "@smithy/util-config-provider" "^4.2.1" - "@smithy/util-middleware" "^4.2.10" - "@smithy/util-stream" "^4.5.16" - "@smithy/util-utf8" "^4.2.1" - tslib "^2.6.2" - -"@aws-sdk/middleware-ssec@^3.972.6": - version "3.972.6" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-ssec/-/middleware-ssec-3.972.6.tgz#aab297072dfcba16c01c2e5073cacf7b3f360ef2" - integrity sha512-acvMUX9jF4I2Ew+Z/EA6gfaFaz9ehci5wxBmXCZeulLuv8m+iGf6pY9uKz8TPjg39bdAz3hxoE0eLP8Qz+IYlA== - dependencies: - "@aws-sdk/types" "^3.973.4" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@aws-sdk/middleware-user-agent@^3.972.17": - version "3.972.17" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.17.tgz#5e3fc8b64b1f44a89df4cf3d677a769cb8d62c91" - integrity sha512-HHArkgWzomuwufXwheQqkddu763PWCpoNTq1dGjqXzJT/lojX3VlOqjNSR2Xvb6/T9ISfwYcMOcbFgUp4EWxXA== - dependencies: - "@aws-sdk/core" "^3.973.17" - "@aws-sdk/types" "^3.973.4" - "@aws-sdk/util-endpoints" "^3.996.3" - "@smithy/core" "^3.23.7" - "@smithy/protocol-http" "^5.3.10" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@aws-sdk/nested-clients@^3.996.5": - version "3.996.5" - resolved "https://registry.yarnpkg.com/@aws-sdk/nested-clients/-/nested-clients-3.996.5.tgz#2634d242bdd1ef04c10f48ec9ceb4b171cb7f99c" - integrity sha512-zn0WApcULn7Rtl6T+KP2CQTZo/7wOa2YV1yHQnbijTQoi4YXQHM8s21JcJzt33/mqPh8AdvWX1f+83KvKuxlZw== - dependencies: - "@aws-crypto/sha256-browser" "5.2.0" - "@aws-crypto/sha256-js" "5.2.0" - "@aws-sdk/core" "^3.973.17" - "@aws-sdk/middleware-host-header" "^3.972.6" - "@aws-sdk/middleware-logger" "^3.972.6" - "@aws-sdk/middleware-recursion-detection" "^3.972.6" - "@aws-sdk/middleware-user-agent" "^3.972.17" - "@aws-sdk/region-config-resolver" "^3.972.6" - "@aws-sdk/types" "^3.973.4" - "@aws-sdk/util-endpoints" "^3.996.3" - "@aws-sdk/util-user-agent-browser" "^3.972.6" - "@aws-sdk/util-user-agent-node" "^3.973.2" - "@smithy/config-resolver" "^4.4.9" - "@smithy/core" "^3.23.7" - "@smithy/fetch-http-handler" "^5.3.12" - "@smithy/hash-node" "^4.2.10" - "@smithy/invalid-dependency" "^4.2.10" - "@smithy/middleware-content-length" "^4.2.10" - "@smithy/middleware-endpoint" "^4.4.21" - "@smithy/middleware-retry" "^4.4.38" - "@smithy/middleware-serde" "^4.2.11" - "@smithy/middleware-stack" "^4.2.10" - "@smithy/node-config-provider" "^4.3.10" - "@smithy/node-http-handler" "^4.4.13" - "@smithy/protocol-http" "^5.3.10" - "@smithy/smithy-client" "^4.12.1" - "@smithy/types" "^4.13.0" - "@smithy/url-parser" "^4.2.10" - "@smithy/util-base64" "^4.3.1" - "@smithy/util-body-length-browser" "^4.2.1" - "@smithy/util-body-length-node" "^4.2.2" - "@smithy/util-defaults-mode-browser" "^4.3.37" - "@smithy/util-defaults-mode-node" "^4.2.40" - "@smithy/util-endpoints" "^3.3.1" - "@smithy/util-middleware" "^4.2.10" - "@smithy/util-retry" "^4.2.10" - "@smithy/util-utf8" "^4.2.1" - tslib "^2.6.2" - -"@aws-sdk/region-config-resolver@^3.972.6": - version "3.972.6" - resolved "https://registry.yarnpkg.com/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.6.tgz#593df7d004d63aca07a9c5799bace7da8deb46cd" - integrity sha512-Aa5PusHLXAqLTX1UKDvI3pHQJtIsF7Q+3turCHqfz/1F61/zDMWfbTC8evjhrrYVAtz9Vsv3SJ/waSUeu7B6gw== - dependencies: - "@aws-sdk/types" "^3.973.4" - "@smithy/config-resolver" "^4.4.9" - "@smithy/node-config-provider" "^4.3.10" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@aws-sdk/signature-v4-multi-region@^3.996.5": - version "3.996.5" - resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.5.tgz#ddf67cfc9aa640bf162032015338d147c1624b7b" - integrity sha512-AVIhf74wRMzU1WBPVzcGPjlADF5VxZ8m8Ctm1v7eO4/reWMhZnEBn4tlR4vM4pOYFkdrYp3MTzYVZIikCO+53Q== - dependencies: - "@aws-sdk/middleware-sdk-s3" "^3.972.17" - "@aws-sdk/types" "^3.973.4" - "@smithy/protocol-http" "^5.3.10" - "@smithy/signature-v4" "^5.3.10" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@aws-sdk/token-providers@3.1002.0": - version "3.1002.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.1002.0.tgz#f65d38725afd4a94a7bc752816ec1aeece502d15" - integrity sha512-x972uKOydFn4Rb0PZJzLdNW59rH0KWC78Q2JbQzZpGlGt0DxjYdDRwBG6F42B1MyaEwHGqO/tkGc4r3/PRFfMw== - dependencies: - "@aws-sdk/core" "^3.973.17" - "@aws-sdk/nested-clients" "^3.996.5" - "@aws-sdk/types" "^3.973.4" - "@smithy/property-provider" "^4.2.10" - "@smithy/shared-ini-file-loader" "^4.4.5" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@aws-sdk/types@^3.222.0", "@aws-sdk/types@^3.973.4": - version "3.973.4" - resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.973.4.tgz#7cb870b08fd83dcb83e4cae321a3108408aaa021" - integrity sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q== - dependencies: - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@aws-sdk/util-arn-parser@^3.972.2": - version "3.972.2" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.2.tgz#ef18ba889e8ef35f083f1e962018bc0ce70acef3" - integrity sha512-VkykWbqMjlSgBFDyrY3nOSqupMc6ivXuGmvci6Q3NnLq5kC+mKQe2QBZ4nrWRE/jqOxeFP2uYzLtwncYYcvQDg== - dependencies: - tslib "^2.6.2" - -"@aws-sdk/util-endpoints@^3.996.3": - version "3.996.3" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-endpoints/-/util-endpoints-3.996.3.tgz#0dd4e7cbdbdb69157aa0a2f60fbcf472dbf0a139" - integrity sha512-yWIQSNiCjykLL+ezN5A+DfBb1gfXTytBxm57e64lYmwxDHNmInYHRJYYRAGWG1o77vKEiWaw4ui28e3yb1k5aQ== - dependencies: - "@aws-sdk/types" "^3.973.4" - "@smithy/types" "^4.13.0" - "@smithy/url-parser" "^4.2.10" - "@smithy/util-endpoints" "^3.3.1" - tslib "^2.6.2" - -"@aws-sdk/util-locate-window@^3.0.0": - version "3.965.4" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-locate-window/-/util-locate-window-3.965.4.tgz#f62d279e1905f6939b6dffb0f76ab925440f72bf" - integrity sha512-H1onv5SkgPBK2P6JR2MjGgbOnttoNzSPIRoeZTNPZYyaplwGg50zS3amXvXqF0/qfXpWEC9rLWU564QTB9bSog== - dependencies: - tslib "^2.6.2" - -"@aws-sdk/util-user-agent-browser@^3.972.6": - version "3.972.6" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.6.tgz#522b5ef758475ec4c4124cfa87c5cb79e1f8719d" - integrity sha512-Fwr/llD6GOrFgQnKaI2glhohdGuBDfHfora6iG9qsBBBR8xv1SdCSwbtf5CWlUdCw5X7g76G/9Hf0Inh0EmoxA== - dependencies: - "@aws-sdk/types" "^3.973.4" - "@smithy/types" "^4.13.0" - bowser "^2.11.0" - tslib "^2.6.2" - -"@aws-sdk/util-user-agent-node@^3.973.2": - version "3.973.2" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.2.tgz#e6669ea0f374c1aba3dc0cd5192e28e771afe64b" - integrity sha512-lpaIuekdkpw7VRiik0IZmd6TyvEUcuLgKZ5fKRGpCA3I4PjrD/XH15sSwW+OptxQjNU4DEzSxag70spC9SluvA== - dependencies: - "@aws-sdk/middleware-user-agent" "^3.972.17" - "@aws-sdk/types" "^3.973.4" - "@smithy/node-config-provider" "^4.3.10" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@aws-sdk/xml-builder@^3.972.9": - version "3.972.9" - resolved "https://registry.yarnpkg.com/@aws-sdk/xml-builder/-/xml-builder-3.972.9.tgz#38a43a0a860c4c73100d727e5b28c43339597b50" - integrity sha512-ItnlMgSqkPrUfJs7EsvU/01zw5UeIb2tNPhD09LBLHbg+g+HDiKibSLwpkuz/ZIlz4F2IMn+5XgE4AK/pfPuog== - dependencies: - "@smithy/types" "^4.13.0" - fast-xml-parser "5.4.1" - tslib "^2.6.2" - -"@aws/lambda-invoke-store@^0.2.2": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz#f1137f56209ccc69c15f826242cbf37f828617dd" - integrity sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw== - -"@bufbuild/protobuf@^1.5.0": - version "1.10.1" - resolved "https://registry.yarnpkg.com/@bufbuild/protobuf/-/protobuf-1.10.1.tgz#1d76d15290c0212076c15ede94d15157ba0c6344" - integrity sha512-wJ8ReQbHxsAfXhrf9ixl0aYbZorRuOWpBNzm8pL8ftmSxQx/wnJD5Eg861NwJU/czy2VXFIebCeZnZrI9rktIQ== - -"@connectrpc/connect-express@^1.1.4": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@connectrpc/connect-express/-/connect-express-1.7.0.tgz#b555f12484121788ebfc5ca6439d5ffdde81cc39" - integrity sha512-Ajj/jupbF0nCgYlUzuKWQHuwLq5XKqQdJYF5aHmnVZlUcg5Z5zp1u4waG2j4JRczuTb8e0R1yVMTnad5SHM3Lg== - -"@connectrpc/connect-node@^1.1.4": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@connectrpc/connect-node/-/connect-node-1.7.0.tgz#e4e78b24462c8a20940f61915f6d8cbd81e3fee5" - integrity sha512-6vaPIkG/NyhxlYgytLoR9KYbPhczEboFB2OYWkA9qvUz1K7efXfeGrlRxoLtpa+r8VxyIOw73w5ktNe743nD+A== - dependencies: - undici "^5.28.4" - -"@connectrpc/connect@^1.1.4": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@connectrpc/connect/-/connect-1.7.0.tgz#03489c61502f70c2c466f97683a2431953915c6a" - integrity sha512-iNKdJRi69YP3mq6AePRT8F/HrxWCewrhxnLMNm0vpqXAR8biwzRtO6Hjx80C6UvtKJ5sFmffQT7I4Baecz389w== - -"@cspotcode/source-map-support@^0.8.0": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" - integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== - dependencies: - "@jridgewell/trace-mapping" "0.3.9" - -"@did-plc/lib@*", "@did-plc/lib@^0.0.4": - version "0.0.4" - resolved "https://registry.yarnpkg.com/@did-plc/lib/-/lib-0.0.4.tgz#be5400dc9464ec3088294bd089631e8a8aa98215" - integrity sha512-Omeawq3b8G/c/5CtkTtzovSOnWuvIuCI4GTJNrt1AmCskwEQV7zbX5d6km1mjJNbE0gHuQPTVqZxLVqetNbfwA== - dependencies: - "@atproto/common" "0.1.1" - "@atproto/crypto" "0.1.0" - "@ipld/dag-cbor" "^7.0.3" - axios "^1.3.4" - multiformats "^9.6.4" - uint8arrays "3.0.0" - zod "^3.14.2" - -"@did-plc/lib@^0.0.1": - version "0.0.1" - resolved "https://registry.yarnpkg.com/@did-plc/lib/-/lib-0.0.1.tgz#5fd78c71901168ac05c5650af3a376c76461991c" - integrity sha512-RkY5w9DbYMco3SjeepqIiMveqz35exjlVDipCs2gz9AXF4/cp9hvmrp9zUWEw2vny+FjV8vGEN7QpaXWaO6nhg== - dependencies: - "@atproto/common" "0.1.0" - "@atproto/crypto" "0.1.0" - "@ipld/dag-cbor" "^7.0.3" - axios "^1.3.4" - multiformats "^9.6.4" - uint8arrays "3.0.0" - zod "^3.14.2" - -"@did-plc/server@^0.0.1": - version "0.0.1" - resolved "https://registry.yarnpkg.com/@did-plc/server/-/server-0.0.1.tgz#8d1ba701f3b2b952b7c8fe03ef3118bb0cba077c" - integrity sha512-GtxxHcOrOQ6fNI1ufq3Zqjc2PtWqPZOdsuzlwtxiH9XibUGwDkb0GmaBHyU5GiOxOKZEW1GspZ8mreBA6XOlTQ== - dependencies: - "@atproto/common" "0.1.0" - "@atproto/crypto" "0.1.0" - "@did-plc/lib" "*" - axios "^1.3.4" - cors "^2.8.5" - express "^4.18.2" - express-async-errors "^3.1.1" - http-terminator "^3.2.0" - kysely "^0.23.4" - multiformats "^9.6.4" - pg "^8.9.0" - pino "^8.11.0" - pino-http "^8.3.3" - -"@emnapi/runtime@^1.2.0": - version "1.8.1" - resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.8.1.tgz#550fa7e3c0d49c5fb175a116e8cd70614f9a22a5" - integrity sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg== - dependencies: - tslib "^2.4.0" - -"@fastify/busboy@^2.0.0": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d" - integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA== - -"@growthbook/growthbook@^1.6.2": - version "1.6.5" - resolved "https://registry.yarnpkg.com/@growthbook/growthbook/-/growthbook-1.6.5.tgz#c9e2119187ee3288525a77a7c64353276f5ba91b" - integrity sha512-mUaMsgeUTpRIUOTn33EUXHRK6j7pxBjwqH4WpQyq+pukjd1AIzWlEa6w7i6bInJUcweGgP2beXZmaP6b6UPn7A== - dependencies: - dom-mutator "^0.6.0" - -"@grpc/grpc-js@^1.8.20": - version "1.14.3" - resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.14.3.tgz#4c9b817a900ae4020ddc28515ae4b52c78cfb8da" - integrity sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA== - dependencies: - "@grpc/proto-loader" "^0.8.0" - "@js-sdsl/ordered-map" "^4.4.2" - -"@grpc/proto-loader@^0.7.8": - version "0.7.15" - resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.15.tgz#4cdfbf35a35461fc843abe8b9e2c0770b5095e60" - integrity sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ== - dependencies: - lodash.camelcase "^4.3.0" - long "^5.0.0" - protobufjs "^7.2.5" - yargs "^17.7.2" - -"@grpc/proto-loader@^0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.8.0.tgz#b6c324dd909c458a0e4aa9bfd3d69cf78a4b9bd8" - integrity sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ== - dependencies: - lodash.camelcase "^4.3.0" - long "^5.0.0" - protobufjs "^7.5.3" - yargs "^17.7.2" - -"@hapi/accept@^6.0.3": - version "6.0.3" - resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-6.0.3.tgz#eef0800a4f89cd969da8e5d0311dc877c37279ab" - integrity sha512-p72f9k56EuF0n3MwlBNThyVE5PXX40g+aQh+C/xbKrfzahM2Oispv3AXmOIU51t3j77zay1qrX7IIziZXspMlw== - dependencies: - "@hapi/boom" "^10.0.1" - "@hapi/hoek" "^11.0.2" - -"@hapi/address@^5.1.1": - version "5.1.1" - resolved "https://registry.yarnpkg.com/@hapi/address/-/address-5.1.1.tgz#e9925fc1b65f5cc3fbea821f2b980e4652e84cb6" - integrity sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA== - dependencies: - "@hapi/hoek" "^11.0.2" - -"@hapi/boom@^10.0.0", "@hapi/boom@^10.0.1": - version "10.0.1" - resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-10.0.1.tgz#ebb14688275ae150aa6af788dbe482e6a6062685" - integrity sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA== - dependencies: - "@hapi/hoek" "^11.0.2" - -"@hapi/bourne@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-3.0.0.tgz#f11fdf7dda62fe8e336fa7c6642d9041f30356d7" - integrity sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w== - -"@hapi/content@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@hapi/content/-/content-6.0.0.tgz#2427af3bac8a2f743512fce2a70cbdc365af29df" - integrity sha512-CEhs7j+H0iQffKfe5Htdak5LBOz/Qc8TRh51cF+BFv0qnuph3Em4pjGVzJMkI2gfTDdlJKWJISGWS1rK34POGA== - dependencies: - "@hapi/boom" "^10.0.0" - -"@hapi/hoek@^11.0.2": - version "11.0.7" - resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-11.0.7.tgz#56a920793e0a42d10e530da9a64cc0d3919c4002" - integrity sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ== - -"@img/sharp-darwin-arm64@0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz#ef5b5a07862805f1e8145a377c8ba6e98813ca08" - integrity sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ== - optionalDependencies: - "@img/sharp-libvips-darwin-arm64" "1.0.4" - -"@img/sharp-darwin-x64@0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz#e03d3451cd9e664faa72948cc70a403ea4063d61" - integrity sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q== - optionalDependencies: - "@img/sharp-libvips-darwin-x64" "1.0.4" - -"@img/sharp-libvips-darwin-arm64@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz#447c5026700c01a993c7804eb8af5f6e9868c07f" - integrity sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg== - -"@img/sharp-libvips-darwin-x64@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz#e0456f8f7c623f9dbfbdc77383caa72281d86062" - integrity sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ== - -"@img/sharp-libvips-linux-arm64@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz#979b1c66c9a91f7ff2893556ef267f90ebe51704" - integrity sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA== - -"@img/sharp-libvips-linux-arm@1.0.5": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz#99f922d4e15216ec205dcb6891b721bfd2884197" - integrity sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g== - -"@img/sharp-libvips-linux-s390x@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz#f8a5eb1f374a082f72b3f45e2fb25b8118a8a5ce" - integrity sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA== - -"@img/sharp-libvips-linux-x64@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz#d4c4619cdd157774906e15770ee119931c7ef5e0" - integrity sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw== - -"@img/sharp-libvips-linuxmusl-arm64@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz#166778da0f48dd2bded1fa3033cee6b588f0d5d5" - integrity sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA== - -"@img/sharp-libvips-linuxmusl-x64@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz#93794e4d7720b077fcad3e02982f2f1c246751ff" - integrity sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw== - -"@img/sharp-linux-arm64@0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz#edb0697e7a8279c9fc829a60fc35644c4839bb22" - integrity sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA== - optionalDependencies: - "@img/sharp-libvips-linux-arm64" "1.0.4" - -"@img/sharp-linux-arm@0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz#422c1a352e7b5832842577dc51602bcd5b6f5eff" - integrity sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ== - optionalDependencies: - "@img/sharp-libvips-linux-arm" "1.0.5" - -"@img/sharp-linux-s390x@0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz#f5c077926b48e97e4a04d004dfaf175972059667" - integrity sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q== - optionalDependencies: - "@img/sharp-libvips-linux-s390x" "1.0.4" - -"@img/sharp-linux-x64@0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz#d806e0afd71ae6775cc87f0da8f2d03a7c2209cb" - integrity sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA== - optionalDependencies: - "@img/sharp-libvips-linux-x64" "1.0.4" - -"@img/sharp-linuxmusl-arm64@0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz#252975b915894fb315af5deea174651e208d3d6b" - integrity sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g== - optionalDependencies: - "@img/sharp-libvips-linuxmusl-arm64" "1.0.4" - -"@img/sharp-linuxmusl-x64@0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz#3f4609ac5d8ef8ec7dadee80b560961a60fd4f48" - integrity sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw== - optionalDependencies: - "@img/sharp-libvips-linuxmusl-x64" "1.0.4" - -"@img/sharp-wasm32@0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz#6f44f3283069d935bb5ca5813153572f3e6f61a1" - integrity sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg== - dependencies: - "@emnapi/runtime" "^1.2.0" - -"@img/sharp-win32-ia32@0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz#1a0c839a40c5351e9885628c85f2e5dfd02b52a9" - integrity sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ== - -"@img/sharp-win32-x64@0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz#56f00962ff0c4e0eb93d34a047d29fa995e3e342" - integrity sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg== - -"@ioredis/commands@1.5.1": - version "1.5.1" - resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.5.1.tgz#a0a3449993b10c7aeb91ecb0d5f1a23692297e51" - integrity sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw== - -"@ipld/dag-cbor@^7.0.0", "@ipld/dag-cbor@^7.0.3": - version "7.0.3" - resolved "https://registry.yarnpkg.com/@ipld/dag-cbor/-/dag-cbor-7.0.3.tgz#aa31b28afb11a807c3d627828a344e5521ac4a1e" - integrity sha512-1VVh2huHsuohdXC1bGJNE8WR72slZ9XE2T3wbBBq31dm7ZBatmKLLxrB+XAqafxfRFjv08RZmj/W/ZqaM13AuA== - dependencies: - cborg "^1.6.0" - multiformats "^9.5.4" - -"@isaacs/cliui@^8.0.2": - version "8.0.2" - resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" - integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== - dependencies: - string-width "^5.1.2" - string-width-cjs "npm:string-width@^4.2.0" - strip-ansi "^7.0.1" - strip-ansi-cjs "npm:strip-ansi@^6.0.1" - wrap-ansi "^8.1.0" - wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" - -"@jridgewell/resolve-uri@^3.0.3": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/sourcemap-codec@^1.4.10": - version "1.5.5" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" - integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== - -"@jridgewell/trace-mapping@0.3.9": - version "0.3.9" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" - integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@js-sdsl/ordered-map@^4.4.2": - version "4.4.2" - resolved "https://registry.yarnpkg.com/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz#9299f82874bab9e4c7f9c48d865becbfe8d6907c" - integrity sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw== - -"@noble/curves@^1.7.0": - version "1.9.7" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.9.7.tgz#79d04b4758a43e4bca2cbdc62e7771352fa6b951" - integrity sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw== - dependencies: - "@noble/hashes" "1.8.0" - -"@noble/hashes@1.8.0", "@noble/hashes@^1.6.1": - version "1.8.0" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a" - integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== - -"@noble/secp256k1@^1.7.0": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.2.tgz#c2c3343e2dce80e15a914d7442147507f8a98e7f" - integrity sha512-/qzwYl5eFLH8OWIecQWM31qld2g1NfjgylK+TNhqtaUKP37Nm+Y+z30Fjhw0Ct8p9yCQEm2N3W/AckdIb3SMcQ== - -"@pkgjs/parseargs@^0.11.0": - version "0.11.0" - resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" - integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== - -"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" - integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== - -"@protobufjs/base64@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" - integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== - -"@protobufjs/codegen@^2.0.4": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" - integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== - -"@protobufjs/eventemitter@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" - integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== - -"@protobufjs/fetch@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" - integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== - dependencies: - "@protobufjs/aspromise" "^1.1.1" - "@protobufjs/inquire" "^1.1.0" - -"@protobufjs/float@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" - integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== - -"@protobufjs/inquire@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" - integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== - -"@protobufjs/path@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" - integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== - -"@protobufjs/pool@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" - integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== - -"@protobufjs/utf8@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" - integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== - -"@smithy/abort-controller@^4.0.5", "@smithy/abort-controller@^4.2.11": - version "4.2.11" - resolved "https://registry.yarnpkg.com/@smithy/abort-controller/-/abort-controller-4.2.11.tgz#b989e63615e5449c2ba90d80fcbe4fdd71123c54" - integrity sha512-Hj4WoYWMJnSpM6/kchsm4bUNTL9XiSyhvoMb2KIq4VJzyDt7JpGHUZHkVNPZVC7YE1tf8tPeVauxpFBKGW4/KQ== - dependencies: - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@smithy/chunked-blob-reader-native@^4.2.3": - version "4.2.3" - resolved "https://registry.yarnpkg.com/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.3.tgz#9e79a80d8d44798e7ce7a8f968cbbbaf5a40d950" - integrity sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw== - dependencies: - "@smithy/util-base64" "^4.3.2" - tslib "^2.6.2" - -"@smithy/chunked-blob-reader@^5.2.2": - version "5.2.2" - resolved "https://registry.yarnpkg.com/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.2.tgz#3af48e37b10e5afed478bb31d2b7bc03c81d196c" - integrity sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw== - dependencies: - tslib "^2.6.2" - -"@smithy/config-resolver@^4.4.10", "@smithy/config-resolver@^4.4.9": - version "4.4.10" - resolved "https://registry.yarnpkg.com/@smithy/config-resolver/-/config-resolver-4.4.10.tgz#22529a2e8c23d979f69c3abca8d984c69d06ce4c" - integrity sha512-IRTkd6ps0ru+lTWnfnsbXzW80A8Od8p3pYiZnW98K2Hb20rqfsX7VTlfUwhrcOeSSy68Gn9WBofwPuw3e5CCsg== - dependencies: - "@smithy/node-config-provider" "^4.3.11" - "@smithy/types" "^4.13.0" - "@smithy/util-config-provider" "^4.2.2" - "@smithy/util-endpoints" "^3.3.2" - "@smithy/util-middleware" "^4.2.11" - tslib "^2.6.2" - -"@smithy/core@^3.23.7", "@smithy/core@^3.23.8": - version "3.23.8" - resolved "https://registry.yarnpkg.com/@smithy/core/-/core-3.23.8.tgz#6bb913a38522337e09b541aedfb078fe7275722d" - integrity sha512-f7uPeBi7ehmLT4YF2u9j3qx6lSnurG1DLXOsTtJrIRNDF7VXio4BGHQ+SQteN/BrUVudbkuL4v7oOsRCzq4BqA== - dependencies: - "@smithy/middleware-serde" "^4.2.12" - "@smithy/protocol-http" "^5.3.11" - "@smithy/types" "^4.13.0" - "@smithy/util-base64" "^4.3.2" - "@smithy/util-body-length-browser" "^4.2.2" - "@smithy/util-middleware" "^4.2.11" - "@smithy/util-stream" "^4.5.17" - "@smithy/util-utf8" "^4.2.2" - "@smithy/uuid" "^1.1.2" - tslib "^2.6.2" - -"@smithy/credential-provider-imds@^4.2.10", "@smithy/credential-provider-imds@^4.2.11": - version "4.2.11" - resolved "https://registry.yarnpkg.com/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.11.tgz#106dda92b2a4275879e84f348826c311a1bb1b05" - integrity sha512-lBXrS6ku0kTj3xLmsJW0WwqWbGQ6ueooYyp/1L9lkyT0M02C+DWwYwc5aTyXFbRaK38ojALxNixg+LxKSHZc0g== - dependencies: - "@smithy/node-config-provider" "^4.3.11" - "@smithy/property-provider" "^4.2.11" - "@smithy/types" "^4.13.0" - "@smithy/url-parser" "^4.2.11" - tslib "^2.6.2" - -"@smithy/eventstream-codec@^4.2.11": - version "4.2.11" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-codec/-/eventstream-codec-4.2.11.tgz#b26d17be447ddb361d7f90af44ff7fb03d8a3e08" - integrity sha512-Sf39Ml0iVX+ba/bgMPxaXWAAFmHqYLTmbjAPfLPLY8CrYkRDEqZdUsKC1OwVMCdJXfAt0v4j49GIJ8DoSYAe6w== - dependencies: - "@aws-crypto/crc32" "5.2.0" - "@smithy/types" "^4.13.0" - "@smithy/util-hex-encoding" "^4.2.2" - tslib "^2.6.2" - -"@smithy/eventstream-serde-browser@^4.2.10": - version "4.2.11" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.11.tgz#9bcaec291d3b5b6a199773ab5d096f395abc22e2" - integrity sha512-3rEpo3G6f/nRS7fQDsZmxw/ius6rnlIpz4UX6FlALEzz8JoSxFmdBt0SZnthis+km7sQo6q5/3e+UJcuQivoXA== - dependencies: - "@smithy/eventstream-serde-universal" "^4.2.11" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@smithy/eventstream-serde-config-resolver@^4.3.10": - version "4.3.11" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.11.tgz#87a30070c7026acdffa5294b0953966d21c588db" - integrity sha512-XeNIA8tcP/GDWnnKkO7qEm/bg0B/bP9lvIXZBXcGZwZ+VYM8h8k9wuDvUODtdQ2Wcp2RcBkPTCSMmaniVHrMlA== - dependencies: - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@smithy/eventstream-serde-node@^4.2.10": - version "4.2.11" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.11.tgz#25a2d6d3d13048be4e62c7211c99d138bddc480e" - integrity sha512-fzbCh18rscBDTQSCrsp1fGcclLNF//nJyhjldsEl/5wCYmgpHblv5JSppQAyQI24lClsFT0wV06N1Porn0IsEw== - dependencies: - "@smithy/eventstream-serde-universal" "^4.2.11" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@smithy/eventstream-serde-universal@^4.2.11": - version "4.2.11" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.11.tgz#c5b5b15c2599441e3d8779bee592fbbbf722878f" - integrity sha512-MJ7HcI+jEkqoWT5vp+uoVaAjBrmxBtKhZTeynDRG/seEjJfqyg3SiqMMqyPnAMzmIfLaeJ/uiuSDP/l9AnMy/Q== - dependencies: - "@smithy/eventstream-codec" "^4.2.11" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@smithy/fetch-http-handler@^5.3.12", "@smithy/fetch-http-handler@^5.3.13": - version "5.3.13" - resolved "https://registry.yarnpkg.com/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.13.tgz#9858e43ff009af6085cca326805c9d0c9a9579f5" - integrity sha512-U2Hcfl2s3XaYjikN9cT4mPu8ybDbImV3baXR0PkVlC0TTx808bRP3FaPGAzPtB8OByI+JqJ1kyS+7GEgae7+qQ== - dependencies: - "@smithy/protocol-http" "^5.3.11" - "@smithy/querystring-builder" "^4.2.11" - "@smithy/types" "^4.13.0" - "@smithy/util-base64" "^4.3.2" - tslib "^2.6.2" - -"@smithy/hash-blob-browser@^4.2.11": - version "4.2.12" - resolved "https://registry.yarnpkg.com/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.12.tgz#daa43ccb485d55187c93e72471e0fd48cae8da7b" - integrity sha512-1wQE33DsxkM/waftAhCH9VtJbUGyt1PJ9YRDpOu+q9FUi73LLFUZ2fD8A61g2mT1UY9k7b99+V1xZ41Rz4SHRQ== - dependencies: - "@smithy/chunked-blob-reader" "^5.2.2" - "@smithy/chunked-blob-reader-native" "^4.2.3" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@smithy/hash-node@^4.2.10": - version "4.2.11" - resolved "https://registry.yarnpkg.com/@smithy/hash-node/-/hash-node-4.2.11.tgz#8b19d53661824ead9627b49a26e5555d6c8a98fd" - integrity sha512-T+p1pNynRkydpdL015ruIoyPSRw9e/SQOWmSAMmmprfswMrd5Ow5igOWNVlvyVFZlxXqGmyH3NQwfwy8r5Jx0A== - dependencies: - "@smithy/types" "^4.13.0" - "@smithy/util-buffer-from" "^4.2.2" - "@smithy/util-utf8" "^4.2.2" - tslib "^2.6.2" - -"@smithy/hash-stream-node@^4.2.10": - version "4.2.11" - resolved "https://registry.yarnpkg.com/@smithy/hash-stream-node/-/hash-stream-node-4.2.11.tgz#30f0236c85c1b900881c01eefe4f329ffe9ef7b1" - integrity sha512-hQsTjwPCRY8w9GK07w1RqJi3e+myh0UaOWBBhZ1UMSDgofH/Q1fEYzU1teaX6HkpX/eWDdm7tAGR0jBPlz9QEQ== - dependencies: - "@smithy/types" "^4.13.0" - "@smithy/util-utf8" "^4.2.2" - tslib "^2.6.2" - -"@smithy/invalid-dependency@^4.2.10": - version "4.2.11" - resolved "https://registry.yarnpkg.com/@smithy/invalid-dependency/-/invalid-dependency-4.2.11.tgz#ded68aa2299474c3cf06695ebb28a343928086ee" - integrity sha512-cGNMrgykRmddrNhYy1yBdrp5GwIgEkniS7k9O1VLB38yxQtlvrxpZtUVvo6T4cKpeZsriukBuuxfJcdZQc/f/g== - dependencies: - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@smithy/is-array-buffer@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz#f84f0d9f9a36601a9ca9381688bd1b726fd39111" - integrity sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA== - dependencies: - tslib "^2.6.2" - -"@smithy/is-array-buffer@^4.2.1", "@smithy/is-array-buffer@^4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz#c401ce54b12a16529eb1c938a0b6c2247cb763b8" - integrity sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow== - dependencies: - tslib "^2.6.2" - -"@smithy/md5-js@^4.2.10": - version "4.2.11" - resolved "https://registry.yarnpkg.com/@smithy/md5-js/-/md5-js-4.2.11.tgz#1bc8b13ad9cb1b47ac6965fca90ac49f6b22efef" - integrity sha512-350X4kGIrty0Snx2OWv7rPM6p6vM7RzryvFs6B/56Cux3w3sChOb3bymo5oidXJlPcP9fIRxGUCk7GqpiSOtng== - dependencies: - "@smithy/types" "^4.13.0" - "@smithy/util-utf8" "^4.2.2" - tslib "^2.6.2" - -"@smithy/middleware-content-length@^4.2.10": - version "4.2.11" - resolved "https://registry.yarnpkg.com/@smithy/middleware-content-length/-/middleware-content-length-4.2.11.tgz#8a385fa77e8fa6ffea6b46e7af37b14d2678571f" - integrity sha512-UvIfKYAKhCzr4p6jFevPlKhQwyQwlJ6IeKLDhmV1PlYfcW3RL4ROjNEDtSik4NYMi9kDkH7eSwyTP3vNJ/u/Dw== - dependencies: - "@smithy/protocol-http" "^5.3.11" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@smithy/middleware-endpoint@^4.1.19", "@smithy/middleware-endpoint@^4.4.21", "@smithy/middleware-endpoint@^4.4.22": - version "4.4.22" - resolved "https://registry.yarnpkg.com/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.22.tgz#5e6f2986e2978cee983393ecd9b71f20fe59359e" - integrity sha512-sc81w1o4Jy+/MAQlY3sQ8C7CmSpcvIi3TAzXblUv2hjG11BBSJi/Cw8vDx5BxMxapuH2I+Gc+45vWsgU07WZRQ== - dependencies: - "@smithy/core" "^3.23.8" - "@smithy/middleware-serde" "^4.2.12" - "@smithy/node-config-provider" "^4.3.11" - "@smithy/shared-ini-file-loader" "^4.4.6" - "@smithy/types" "^4.13.0" - "@smithy/url-parser" "^4.2.11" - "@smithy/util-middleware" "^4.2.11" - tslib "^2.6.2" - -"@smithy/middleware-retry@^4.4.38": - version "4.4.39" - resolved "https://registry.yarnpkg.com/@smithy/middleware-retry/-/middleware-retry-4.4.39.tgz#0bc7648bab14aff4866255af2e84c907ae7fe8f4" - integrity sha512-MCVCxaCzuZgiHtHGV2Ke44nh6t4+8/tO+rTYOzrr2+G4nMLU/qbzNCWKBX54lyEaVcGQrfOJiG2f8imtiw+nIQ== - dependencies: - "@smithy/node-config-provider" "^4.3.11" - "@smithy/protocol-http" "^5.3.11" - "@smithy/service-error-classification" "^4.2.11" - "@smithy/smithy-client" "^4.12.2" - "@smithy/types" "^4.13.0" - "@smithy/util-middleware" "^4.2.11" - "@smithy/util-retry" "^4.2.11" - "@smithy/uuid" "^1.1.2" - tslib "^2.6.2" - -"@smithy/middleware-serde@^4.2.11", "@smithy/middleware-serde@^4.2.12": - version "4.2.12" - resolved "https://registry.yarnpkg.com/@smithy/middleware-serde/-/middleware-serde-4.2.12.tgz#8f836f3edc85701b69df4f2819106a6e0ef50cf8" - integrity sha512-W9g1bOLui7Xn5FABRVS0o3rXL0gfN37d/8I/W7i0N7oxjx9QecUmXEMSUMADTODwdtka9cN43t5BI2CodLJpng== - dependencies: - "@smithy/protocol-http" "^5.3.11" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@smithy/middleware-stack@^4.2.10", "@smithy/middleware-stack@^4.2.11": - version "4.2.11" - resolved "https://registry.yarnpkg.com/@smithy/middleware-stack/-/middleware-stack-4.2.11.tgz#cadd3ada5fa11fe8a192cd18444a77c4510c8bc3" - integrity sha512-s+eenEPW6RgliDk2IhjD2hWOxIx1NKrOHxEwNUaUXxYBxIyCcDfNULZ2Mu15E3kwcJWBedTET/kEASPV1A1Akg== - dependencies: - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@smithy/node-config-provider@^4.3.10", "@smithy/node-config-provider@^4.3.11": - version "4.3.11" - resolved "https://registry.yarnpkg.com/@smithy/node-config-provider/-/node-config-provider-4.3.11.tgz#a6d246b67c10c6873169bae46e6d04261d548402" - integrity sha512-xD17eE7kaLgBBGf5CZQ58hh2YmwK1Z0O8YhffwB/De2jsL0U3JklmhVYJ9Uf37OtUDLF2gsW40Xwwag9U869Gg== - dependencies: - "@smithy/property-provider" "^4.2.11" - "@smithy/shared-ini-file-loader" "^4.4.6" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@smithy/node-http-handler@^4.4.13", "@smithy/node-http-handler@^4.4.14": - version "4.4.14" - resolved "https://registry.yarnpkg.com/@smithy/node-http-handler/-/node-http-handler-4.4.14.tgz#a40a6677b7cda2c100141833abee1401c2e1a74f" - integrity sha512-DamSqaU8nuk0xTJDrYnRzZndHwwRnyj/n/+RqGGCcBKB4qrQem0mSDiWdupaNWdwxzyMU91qxDmHOCazfhtO3A== - dependencies: - "@smithy/abort-controller" "^4.2.11" - "@smithy/protocol-http" "^5.3.11" - "@smithy/querystring-builder" "^4.2.11" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@smithy/property-provider@^4.2.10", "@smithy/property-provider@^4.2.11": - version "4.2.11" - resolved "https://registry.yarnpkg.com/@smithy/property-provider/-/property-provider-4.2.11.tgz#7a1b16ae2083272f80e380ee7948ddc103301db1" - integrity sha512-14T1V64o6/ndyrnl1ze1ZhyLzIeYNN47oF/QU6P5m82AEtyOkMJTb0gO1dPubYjyyKuPD6OSVMPDKe+zioOnCg== - dependencies: - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@smithy/protocol-http@^5.3.10", "@smithy/protocol-http@^5.3.11": - version "5.3.11" - resolved "https://registry.yarnpkg.com/@smithy/protocol-http/-/protocol-http-5.3.11.tgz#e4450af3ba9e52e8b99a9c3035c90c8cd853be27" - integrity sha512-hI+barOVDJBkNt4y0L2mu3Ugc0w7+BpJ2CZuLwXtSltGAAwCb3IvnalGlbDV/UCS6a9ZuT3+exd1WxNdLb5IlQ== - dependencies: - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@smithy/querystring-builder@^4.2.11": - version "4.2.11" - resolved "https://registry.yarnpkg.com/@smithy/querystring-builder/-/querystring-builder-4.2.11.tgz#befb7753b142fab65edaee070096c1c5cb2ad917" - integrity sha512-7spdikrYiljpket6u0up2Ck2mxhy7dZ0+TDd+S53Dg2DHd6wg+YNJrTCHiLdgZmEXZKI7LJZcwL3721ZRDFiqA== - dependencies: - "@smithy/types" "^4.13.0" - "@smithy/util-uri-escape" "^4.2.2" - tslib "^2.6.2" - -"@smithy/querystring-parser@^4.2.11": - version "4.2.11" - resolved "https://registry.yarnpkg.com/@smithy/querystring-parser/-/querystring-parser-4.2.11.tgz#b1e85945bc3c80058e0b0114af391bb069b2393f" - integrity sha512-nE3IRNjDltvGcoThD2abTozI1dkSy8aX+a2N1Rs55en5UsdyyIXgGEmevUL3okZFoJC77JgRGe99xYohhsjivQ== - dependencies: - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@smithy/service-error-classification@^4.2.11": - version "4.2.11" - resolved "https://registry.yarnpkg.com/@smithy/service-error-classification/-/service-error-classification-4.2.11.tgz#da2ee1af5c851380e6b0146b75416f0e5f64e1f7" - integrity sha512-HkMFJZJUhzU3HvND1+Yw/kYWXp4RPDLBWLcK1n+Vqw8xn4y2YiBhdww8IxhkQjP/QlZun5bwm3vcHc8AqIU3zw== - dependencies: - "@smithy/types" "^4.13.0" - -"@smithy/shared-ini-file-loader@^4.4.5", "@smithy/shared-ini-file-loader@^4.4.6": - version "4.4.6" - resolved "https://registry.yarnpkg.com/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.6.tgz#435dc6d907bc8c6f795212e944000de063b2cfe1" - integrity sha512-IB/M5I8G0EeXZTHsAxpx51tMQ5R719F3aq+fjEB6VtNcCHDc0ajFDIGDZw+FW9GxtEkgTduiPpjveJdA/CX7sw== - dependencies: - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@smithy/signature-v4@^5.3.10": - version "5.3.11" - resolved "https://registry.yarnpkg.com/@smithy/signature-v4/-/signature-v4-5.3.11.tgz#81fc2aba69994b23aff730b984418e9696bc36c4" - integrity sha512-V1L6N9aKOBAN4wEHLyqjLBnAz13mtILU0SeDrjOaIZEeN6IFa6DxwRt1NNpOdmSpQUfkBj0qeD3m6P77uzMhgQ== - dependencies: - "@smithy/is-array-buffer" "^4.2.2" - "@smithy/protocol-http" "^5.3.11" - "@smithy/types" "^4.13.0" - "@smithy/util-hex-encoding" "^4.2.2" - "@smithy/util-middleware" "^4.2.11" - "@smithy/util-uri-escape" "^4.2.2" - "@smithy/util-utf8" "^4.2.2" - tslib "^2.6.2" - -"@smithy/smithy-client@^4.12.1", "@smithy/smithy-client@^4.12.2", "@smithy/smithy-client@^4.5.0": - version "4.12.2" - resolved "https://registry.yarnpkg.com/@smithy/smithy-client/-/smithy-client-4.12.2.tgz#feaf2ddd9e568da4abaf6a2663995770d241f5b3" - integrity sha512-HezY3UuG0k4T+4xhFKctLXCA5N2oN+Rtv+mmL8Gt7YmsUY2yhmcLyW75qrSzldfj75IsCW/4UhY3s20KcFnZqA== - dependencies: - "@smithy/core" "^3.23.8" - "@smithy/middleware-endpoint" "^4.4.22" - "@smithy/middleware-stack" "^4.2.11" - "@smithy/protocol-http" "^5.3.11" - "@smithy/types" "^4.13.0" - "@smithy/util-stream" "^4.5.17" - tslib "^2.6.2" - -"@smithy/types@^4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@smithy/types/-/types-4.13.0.tgz#9787297a07ee72ef74d4f7d93c744d10ed664c21" - integrity sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw== - dependencies: - tslib "^2.6.2" - -"@smithy/url-parser@^4.2.10", "@smithy/url-parser@^4.2.11": - version "4.2.11" - resolved "https://registry.yarnpkg.com/@smithy/url-parser/-/url-parser-4.2.11.tgz#4c87eb5872c2ab0385086b38eee4b4a6e5a029b2" - integrity sha512-oTAGGHo8ZYc5VZsBREzuf5lf2pAurJQsccMusVZ85wDkX66ojEc/XauiGjzCj50A61ObFTPe6d7Pyt6UBYaing== - dependencies: - "@smithy/querystring-parser" "^4.2.11" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@smithy/util-base64@^4.3.1", "@smithy/util-base64@^4.3.2": - version "4.3.2" - resolved "https://registry.yarnpkg.com/@smithy/util-base64/-/util-base64-4.3.2.tgz#be02bcb29a87be744356467ea25ffa413e695cea" - integrity sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ== - dependencies: - "@smithy/util-buffer-from" "^4.2.2" - "@smithy/util-utf8" "^4.2.2" - tslib "^2.6.2" - -"@smithy/util-body-length-browser@^4.2.1", "@smithy/util-body-length-browser@^4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.2.tgz#c4404277d22039872abdb80e7800f9a63f263862" - integrity sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ== - dependencies: - tslib "^2.6.2" - -"@smithy/util-body-length-node@^4.2.2": - version "4.2.3" - resolved "https://registry.yarnpkg.com/@smithy/util-body-length-node/-/util-body-length-node-4.2.3.tgz#f923ca530defb86a9ac3ca2d3066bcca7b304fbc" - integrity sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g== - dependencies: - tslib "^2.6.2" - -"@smithy/util-buffer-from@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz#6fc88585165ec73f8681d426d96de5d402021e4b" - integrity sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA== - dependencies: - "@smithy/is-array-buffer" "^2.2.0" - tslib "^2.6.2" - -"@smithy/util-buffer-from@^4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz#2c6b7857757dfd88f6cd2d36016179a40ccc913b" - integrity sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q== - dependencies: - "@smithy/is-array-buffer" "^4.2.2" - tslib "^2.6.2" - -"@smithy/util-config-provider@^4.2.1", "@smithy/util-config-provider@^4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@smithy/util-config-provider/-/util-config-provider-4.2.2.tgz#52ebf9d8942838d18bc5fb1520de1e8699d7aad6" - integrity sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ== - dependencies: - tslib "^2.6.2" - -"@smithy/util-defaults-mode-browser@^4.3.37": - version "4.3.38" - resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.38.tgz#c9cb0e0c871b4098e0f234f2426e50fbc8382903" - integrity sha512-c8P1mFLNxcsdAMabB8/VUQUbWzFmgujWi4bAXSggcqLYPc8V4U5abqFqOyn+dK4YT+q8UyCVkTO8807t4t2syA== - dependencies: - "@smithy/property-provider" "^4.2.11" - "@smithy/smithy-client" "^4.12.2" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@smithy/util-defaults-mode-node@^4.2.40": - version "4.2.41" - resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.41.tgz#b1393ef3f4182f72bf1847efcda59961c6d35378" - integrity sha512-/UG+9MT3UZAR0fLzOtMJMfWGcjjHvgggq924x/CRy8vRbL+yFf3Z6vETlvq8vDH92+31P/1gSOFoo7303wN8WQ== - dependencies: - "@smithy/config-resolver" "^4.4.10" - "@smithy/credential-provider-imds" "^4.2.11" - "@smithy/node-config-provider" "^4.3.11" - "@smithy/property-provider" "^4.2.11" - "@smithy/smithy-client" "^4.12.2" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@smithy/util-endpoints@^3.3.1", "@smithy/util-endpoints@^3.3.2": - version "3.3.2" - resolved "https://registry.yarnpkg.com/@smithy/util-endpoints/-/util-endpoints-3.3.2.tgz#a81ee98a2596248f6cdedc868d13cb6b9ea497b2" - integrity sha512-+4HFLpE5u29AbFlTdlKIT7jfOzZ8PDYZKTb3e+AgLz986OYwqTourQ5H+jg79/66DB69Un1+qKecLnkZdAsYcA== - dependencies: - "@smithy/node-config-provider" "^4.3.11" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@smithy/util-hex-encoding@^4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz#4abf3335dd1eb884041d8589ca7628d81a6fd1d3" - integrity sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg== - dependencies: - tslib "^2.6.2" - -"@smithy/util-middleware@^4.2.10", "@smithy/util-middleware@^4.2.11": - version "4.2.11" - resolved "https://registry.yarnpkg.com/@smithy/util-middleware/-/util-middleware-4.2.11.tgz#d2a89893fc2dfd500de412c5f7c7961716855f4d" - integrity sha512-r3dtF9F+TpSZUxpOVVtPfk09Rlo4lT6ORBqEvX3IBT6SkQAdDSVKR5GcfmZbtl7WKhKnmb3wbDTQ6ibR2XHClw== - dependencies: - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@smithy/util-retry@^4.2.10", "@smithy/util-retry@^4.2.11": - version "4.2.11" - resolved "https://registry.yarnpkg.com/@smithy/util-retry/-/util-retry-4.2.11.tgz#59fc5364488d4c755eec5afb4054623f852cf0e6" - integrity sha512-XSZULmL5x6aCTTii59wJqKsY1l3eMIAomRAccW7Tzh9r8s7T/7rdo03oektuH5jeYRlJMPcNP92EuRDvk9aXbw== - dependencies: - "@smithy/service-error-classification" "^4.2.11" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@smithy/util-stream@^4.5.16", "@smithy/util-stream@^4.5.17": - version "4.5.17" - resolved "https://registry.yarnpkg.com/@smithy/util-stream/-/util-stream-4.5.17.tgz#53073153deb890d91fd14fd2055e6582b627b0fd" - integrity sha512-793BYZ4h2JAQkNHcEnyFxDTcZbm9bVybD0UV/LEWmZ5bkTms7JqjfrLMi2Qy0E5WFcCzLwCAPgcvcvxoeALbAQ== - dependencies: - "@smithy/fetch-http-handler" "^5.3.13" - "@smithy/node-http-handler" "^4.4.14" - "@smithy/types" "^4.13.0" - "@smithy/util-base64" "^4.3.2" - "@smithy/util-buffer-from" "^4.2.2" - "@smithy/util-hex-encoding" "^4.2.2" - "@smithy/util-utf8" "^4.2.2" - tslib "^2.6.2" - -"@smithy/util-uri-escape@^4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz#48e40206e7fe9daefc8d44bb43a1ab17e76abf4a" - integrity sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw== - dependencies: - tslib "^2.6.2" - -"@smithy/util-utf8@^2.0.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-2.3.0.tgz#dd96d7640363259924a214313c3cf16e7dd329c5" - integrity sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A== - dependencies: - "@smithy/util-buffer-from" "^2.2.0" - tslib "^2.6.2" - -"@smithy/util-utf8@^4.2.1", "@smithy/util-utf8@^4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-4.2.2.tgz#21db686982e6f3393ac262e49143b42370130f13" - integrity sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw== - dependencies: - "@smithy/util-buffer-from" "^4.2.2" - tslib "^2.6.2" - -"@smithy/util-waiter@^4.2.10": - version "4.2.11" - resolved "https://registry.yarnpkg.com/@smithy/util-waiter/-/util-waiter-4.2.11.tgz#afe08ad75c9b51e35c83e3c11926855d886741f6" - integrity sha512-x7Rh2azQPs3XxbvCzcttRErKKvLnbZfqRf/gOjw2pb+ZscX88e5UkRPCB67bVnsFHxayvMvmePfKTqsRb+is1A== - dependencies: - "@smithy/abort-controller" "^4.2.11" - "@smithy/types" "^4.13.0" - tslib "^2.6.2" - -"@smithy/uuid@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@smithy/uuid/-/uuid-1.1.2.tgz#b6e97c7158615e4a3c775e809c00d8c269b5a12e" - integrity sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g== - dependencies: - tslib "^2.6.2" - -"@standard-schema/spec@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.1.0.tgz#a79b55dbaf8604812f52d140b2c9ab41bc150bb8" - integrity sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w== - -"@tokenizer/token@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" - integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== - -"@tsconfig/node10@^1.0.7": - version "1.0.12" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.12.tgz#be57ceac1e4692b41be9de6be8c32a106636dba4" - integrity sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ== - -"@tsconfig/node12@^1.0.7": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" - integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== - -"@tsconfig/node14@^1.0.0": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" - integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== - -"@tsconfig/node16@^1.0.2": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" - integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== - -"@types/bn.js@*": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.2.0.tgz#4349b9710e98f9ab3cdc50f1c5e4dcbd8ef29c80" - integrity sha512-DLbJ1BPqxvQhIGbeu8VbUC1DiAiahHtAYvA0ZEAa4P31F7IaArc8z3C3BRQdWX4mtLQuABG4yzp76ZrS02Ui1Q== - dependencies: - "@types/node" "*" - -"@types/elliptic@^6.4.9": - version "6.4.18" - resolved "https://registry.yarnpkg.com/@types/elliptic/-/elliptic-6.4.18.tgz#bc96e26e1ccccbabe8b6f0e409c85898635482e1" - integrity sha512-UseG6H5vjRiNpQvrhy4VF/JXdA3V/Fp5amvveaL+fs28BZ6xIKJBPnUPRlEaZpysD9MbpfaLi8lbl7PGUAkpWw== - dependencies: - "@types/bn.js" "*" - -"@types/http-errors@^2.0.1": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" - integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== - -"@types/node@*", "@types/node@>=13.7.0": - version "25.3.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-25.3.3.tgz#605862544ee7ffd7a936bcbf0135a14012f1e549" - integrity sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ== - dependencies: - undici-types "~7.18.0" - -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - -accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - -acorn-walk@^8.1.1: - version "8.3.5" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.5.tgz#8a6b8ca8fc5b34685af15dabb44118663c296496" - integrity sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw== - dependencies: - acorn "^8.11.0" - -acorn@^8.11.0, acorn@^8.4.1: - version "8.16.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a" - integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-regex@^6.2.2: - version "6.2.2" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.2.2.tgz#60216eea464d864597ce2832000738a0589650c1" - integrity sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg== - -ansi-styles@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^6.1.0: - version "6.2.3" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.3.tgz#c044d5dcc521a076413472597a1acb1f103c4041" - integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== - -arg@^4.1.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" - integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== - -asn1.js@^5.0.1: - version "5.4.1" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" - integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== - dependencies: - bn.js "^4.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - safer-buffer "^2.1.0" - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - -atomic-sleep@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" - integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== - -await-lock@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/await-lock/-/await-lock-2.2.2.tgz#a95a9b269bfd2f69d22b17a321686f551152bcef" - integrity sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw== - -axios@^1.3.4: - version "1.13.6" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.13.6.tgz#c3f92da917dc209a15dd29936d20d5089b6b6c98" - integrity sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ== - dependencies: - follow-redirects "^1.15.11" - form-data "^4.0.5" - proxy-from-env "^1.1.0" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base64-js@^1.0.2, base64-js@^1.3.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -better-sqlite3@^10.0.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/better-sqlite3/-/better-sqlite3-10.1.0.tgz#8dc07e496fc014a7cd2211f79e591f6ba92838e8" - integrity sha512-hqpHJaCfKEZFaAWdMh6crdzRWyzQzfP6Ih8TYI0vFn01a6ZTDSbJIMXN+6AMBaBOh99DzUy8l3PsV9R3qnJDng== - dependencies: - bindings "^1.5.0" - prebuild-install "^7.1.1" - -big-integer@^1.6.51: - version "1.6.52" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85" - integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg== - -bignumber.js@^9.1.1: - version "9.3.1" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.3.1.tgz#759c5aaddf2ffdc4f154f7b493e1c8770f88c4d7" - integrity sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ== - -bindings@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" - integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== - dependencies: - file-uri-to-path "1.0.0" - -bl@^4.0.3: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" - integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - -bn.js@^4.0.0, bn.js@^4.11.8, bn.js@^4.11.9: - version "4.12.3" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.3.tgz#2cc2c679188eb35b006f2d0d4710bed8437a769e" - integrity sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g== - -body-parser@~1.20.3: - version "1.20.4" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.4.tgz#f8e20f4d06ca8a50a71ed329c15dccad1cdc547f" - integrity sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA== - dependencies: - bytes "~3.1.2" - content-type "~1.0.5" - debug "2.6.9" - depd "2.0.0" - destroy "~1.2.0" - http-errors "~2.0.1" - iconv-lite "~0.4.24" - on-finished "~2.4.1" - qs "~6.14.0" - raw-body "~2.5.3" - type-is "~1.6.18" - unpipe "~1.0.0" - -bowser@^2.11.0: - version "2.14.1" - resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.14.1.tgz#4ea39bf31e305184522d7ad7bfd91389e4f0cb79" - integrity sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg== - -brace-expansion@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" - integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== - dependencies: - balanced-match "^1.0.0" - -brorand@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" - integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== - -buffer@5.6.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" - integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - -buffer@^5.5.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.1.13" - -buffer@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" - integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.2.1" - -bytes@3.1.2, bytes@^3.1.2, bytes@~3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - -call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" - integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - -call-bound@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" - integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== - dependencies: - call-bind-apply-helpers "^1.0.2" - get-intrinsic "^1.3.0" - -cborg@^1.6.0: - version "1.10.2" - resolved "https://registry.yarnpkg.com/cborg/-/cborg-1.10.2.tgz#83cd581b55b3574c816f82696307c7512db759a1" - integrity sha512-b3tFPA9pUr2zCUiCfRd2+wok2/LBSNUMKOuRRok+WlvvAgEt/PlbgPTsZUcwCOs53IJvLgTp0eotwtosE6njug== - -chownr@^1.1.1: - version "1.1.4" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" - integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== - -cliui@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" - integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.1" - wrap-ansi "^7.0.0" - -cluster-key-slot@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac" - integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== - -cockatiel@^3.1.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/cockatiel/-/cockatiel-3.2.1.tgz#575f937bc4040a20ae27352a6d07c9c5a741981f" - integrity sha512-gfrHV6ZPkquExvMh9IOkKsBzNDk6sDuZ6DdBGUBkvFnTCqCxzpuq48RySgP0AnaqQkw2zynOFj9yly6T1Q2G5Q== - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@^1.0.0, color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -color-string@^1.9.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" - integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - -color@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" - integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== - dependencies: - color-convert "^2.0.1" - color-string "^1.9.0" - -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -compressible@~2.0.18: - version "2.0.18" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" - integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== - dependencies: - mime-db ">= 1.43.0 < 2" - -compression@^1.7.4: - version "1.8.1" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.8.1.tgz#4a45d909ac16509195a9a28bd91094889c180d79" - integrity sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w== - dependencies: - bytes "3.1.2" - compressible "~2.0.18" - debug "2.6.9" - negotiator "~0.6.4" - on-headers "~1.1.0" - safe-buffer "5.2.1" - vary "~1.1.2" - -content-disposition@~0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -content-type@~1.0.4, content-type@~1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" - integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== - -cookie-signature@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.7.tgz#ab5dd7ab757c54e60f37ef6550f481c426d10454" - integrity sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA== - -cookie@^0.7.0, cookie@~0.7.1: - version "0.7.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" - integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== - -core-js@^3: - version "3.48.0" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.48.0.tgz#1f813220a47bbf0e667e3885c36cd6f0593bf14d" - integrity sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ== - -cors@^2.8.5: - version "2.8.6" - resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.6.tgz#ff5dd69bd95e547503820d29aba4f8faf8dfec96" - integrity sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw== - dependencies: - object-assign "^4" - vary "^1" - -create-require@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" - integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== - -cross-spawn@^7.0.6: - version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" - integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -debug@2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@^4.3.4: - version "4.4.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" - integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== - dependencies: - ms "^2.1.3" - -decompress-response@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" - integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== - dependencies: - mimic-response "^3.1.0" - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - -deepmerge@^4.2.2: - version "4.3.1" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" - integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== - -delay@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" - integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== - -denque@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" - integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== - -depd@2.0.0, depd@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -destroy@1.2.0, destroy@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - -detect-libc@^2.0.0, detect-libc@^2.0.3: - version "2.1.2" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad" - integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== - -diff@^4.0.1: - version "4.0.4" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.4.tgz#7a6dbfda325f25f07517e9b518f897c08332e07d" - integrity sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ== - -disposable-email-domains-js@^1.5.0: - version "1.22.0" - resolved "https://registry.yarnpkg.com/disposable-email-domains-js/-/disposable-email-domains-js-1.22.0.tgz#cc9f425cc3316a1b1ef356440bd4c2e7aba0bc0b" - integrity sha512-5miXovN3Qujy07FczD1Z7To6UfHv6U6AhGy2DNXPaD7+Ga1kzR0l5IRNnRuIOVcPgBZ8n855kubx1/ZECDXI/Q== - -dom-mutator@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/dom-mutator/-/dom-mutator-0.6.0.tgz#079d7a4b3e8981a562cd777548b99baab51d65c5" - integrity sha512-iCt9o0aYfXMUkz/43ZOAUFQYotjGB+GNbYJiJdz4TgXkyToXbbRy5S6FbTp72lRBtfpUMwEc1KmpFEU4CZeoNg== - -dom-serializer@^1.0.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" - integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.2.0" - entities "^2.0.0" - -domelementtype@^2.0.1, domelementtype@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" - integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== - -domhandler@^4.0.0, domhandler@^4.2.0: - version "4.3.1" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" - integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== - dependencies: - domelementtype "^2.2.0" - -domutils@^2.5.2: - version "2.8.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" - integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== - dependencies: - dom-serializer "^1.0.1" - domelementtype "^2.2.0" - domhandler "^4.2.0" - -dotenv@^16.0.3: - version "16.6.1" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.6.1.tgz#773f0e69527a8315c7285d5ee73c4459d20a8020" - integrity sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow== - -dunder-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" - integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== - dependencies: - call-bind-apply-helpers "^1.0.1" - es-errors "^1.3.0" - gopd "^1.2.0" - -eastasianwidth@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" - integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - -elliptic@^6.4.1: - version "6.6.1" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.6.1.tgz#3b8ffb02670bf69e382c7f65bf524c97c5405c06" - integrity sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g== - dependencies: - bn.js "^4.11.9" - brorand "^1.1.0" - hash.js "^1.0.0" - hmac-drbg "^1.0.1" - inherits "^2.0.4" - minimalistic-assert "^1.0.1" - minimalistic-crypto-utils "^1.0.1" - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -emoji-regex@^9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" - integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== - -encodeurl@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" - integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== - -end-of-stream@^1.1.0, end-of-stream@^1.4.1: - version "1.4.5" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.5.tgz#7344d711dea40e0b74abc2ed49778743ccedb08c" - integrity sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg== - dependencies: - once "^1.4.0" - -entities@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" - integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== - -es-define-property@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" - integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== - -es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" - integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== - dependencies: - es-errors "^1.3.0" - -es-set-tostringtag@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" - integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== - dependencies: - es-errors "^1.3.0" - get-intrinsic "^1.2.6" - has-tostringtag "^1.0.2" - hasown "^2.0.2" - -escalade@^3.1.1: - version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - -etcd3@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/etcd3/-/etcd3-1.1.2.tgz#0cb0f101b8c9c14ba3a0ae5b9f1fc7de0f04b662" - integrity sha512-YIampCz1/OmrVo/tR3QltAVUtYCQQOSFoqmHKKeoHbalm+WdXe3l4rhLIylklu8EzR/I3PBiOF4dC847dDskKg== - dependencies: - "@grpc/grpc-js" "^1.8.20" - "@grpc/proto-loader" "^0.7.8" - bignumber.js "^9.1.1" - cockatiel "^3.1.1" - -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - -eventemitter3@^4.0.4: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - -events@3.3.0, events@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -expand-template@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" - integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== - -express-async-errors@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/express-async-errors/-/express-async-errors-3.1.1.tgz#6053236d61d21ddef4892d6bd1d736889fc9da41" - integrity sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng== - -express@^4.17.2, express@^4.18.2: - version "4.22.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.22.1.tgz#1de23a09745a4fffdb39247b344bb5eaff382069" - integrity sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "~1.20.3" - content-disposition "~0.5.4" - content-type "~1.0.4" - cookie "~0.7.1" - cookie-signature "~1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~2.0.0" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "~1.3.1" - fresh "~0.5.2" - http-errors "~2.0.0" - merge-descriptors "1.0.3" - methods "~1.1.2" - on-finished "~2.4.1" - parseurl "~1.3.3" - path-to-regexp "~0.1.12" - proxy-addr "~2.0.7" - qs "~6.14.0" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "~0.19.0" - serve-static "~1.16.2" - setprototypeof "1.2.0" - statuses "~2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -fast-printf@^1.6.9: - version "1.6.10" - resolved "https://registry.yarnpkg.com/fast-printf/-/fast-printf-1.6.10.tgz#c44ad871726152159d7a903a5af0d65cf3d75875" - integrity sha512-GwTgG9O4FVIdShhbVF3JxOgSBY2+ePGsu2V/UONgoCPzF9VY6ZdBMKsHKCYQHZwNk3qNouUolRDsgVxcVA5G1w== - -fast-redact@^3.1.1: - version "3.5.0" - resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.5.0.tgz#e9ea02f7e57d0cd8438180083e93077e496285e4" - integrity sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A== - -fast-xml-builder@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fast-xml-builder/-/fast-xml-builder-1.0.0.tgz#a485d7e8381f1db983cf006f849d1066e2935241" - integrity sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ== - -fast-xml-parser@5.4.1: - version "5.4.1" - resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz#0c81b8ecfb3021e5ad83aa3df904af19a05bc601" - integrity sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A== - dependencies: - fast-xml-builder "^1.0.0" - strnum "^2.1.2" - -file-type@^16.5.4: - version "16.5.4" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-16.5.4.tgz#474fb4f704bee427681f98dd390058a172a6c2fd" - integrity sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw== - dependencies: - readable-web-to-node-stream "^3.0.0" - strtok3 "^6.2.4" - token-types "^4.1.1" - -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== - -finalhandler@~1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.2.tgz#1ebc2228fc7673aac4a472c310cc05b77d852b88" - integrity sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg== - dependencies: - debug "2.6.9" - encodeurl "~2.0.0" - escape-html "~1.0.3" - on-finished "~2.4.1" - parseurl "~1.3.3" - statuses "~2.0.2" - unpipe "~1.0.0" - -follow-redirects@^1.15.11: - version "1.15.11" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" - integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== - -foreground-child@^3.1.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" - integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== - dependencies: - cross-spawn "^7.0.6" - signal-exit "^4.0.1" - -form-data@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.5.tgz#b49e48858045ff4cbf6b03e1805cebcad3679053" - integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - es-set-tostringtag "^2.1.0" - hasown "^2.0.2" - mime-types "^2.1.12" - -forwarded@0.2.0, forwarded@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - -french-badwords-list@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/french-badwords-list/-/french-badwords-list-1.0.7.tgz#8ed2442cb20fa2781c38b5a75c944f8e91a73c08" - integrity sha512-H1ziKs2PJh2+UXZ9oCGJ/rRQpsI9NBykGf2Sc7WaKaj1OnWFuBXfsvANTdRcfVmOghGQaUmRyZ1hJOPbDpy04Q== - -fresh@~0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== - -fs-constants@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" - integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" - integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== - dependencies: - call-bind-apply-helpers "^1.0.2" - es-define-property "^1.0.1" - es-errors "^1.3.0" - es-object-atoms "^1.1.1" - function-bind "^1.1.2" - get-proto "^1.0.1" - gopd "^1.2.0" - has-symbols "^1.1.0" - hasown "^2.0.2" - math-intrinsics "^1.1.0" - -get-port@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193" - integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ== - -get-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" - integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== - dependencies: - dunder-proto "^1.0.1" - es-object-atoms "^1.0.0" - -github-from-package@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" - integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== - -glob@^10.3.10: - version "10.5.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.5.0.tgz#8ec0355919cd3338c28428a23d4f24ecc5fe738c" - integrity sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg== - dependencies: - foreground-child "^3.1.0" - jackspeak "^3.1.2" - minimatch "^9.0.4" - minipass "^7.1.2" - package-json-from-dist "^1.0.0" - path-scurry "^1.11.1" - -gopd@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" - integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== - -handlebars@^4.7.7: - version "4.7.8" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" - integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== - dependencies: - minimist "^1.2.5" - neo-async "^2.6.2" - source-map "^0.6.1" - wordwrap "^1.0.0" - optionalDependencies: - uglify-js "^3.1.4" - -has-symbols@^1.0.3, has-symbols@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" - integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== - -has-tostringtag@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" - integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== - dependencies: - has-symbols "^1.0.3" - -hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" - integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.1" - -hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - -he@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -hmac-drbg@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" - integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== - dependencies: - hash.js "^1.0.3" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.1" - -html-to-text@7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/html-to-text/-/html-to-text-7.1.1.tgz#69de8d85b91646b4bc14fdf4f850e9e046efff15" - integrity sha512-c9QWysrfnRZevVpS8MlE7PyOdSuIOjg8Bt8ZE10jMU/BEngA6j3llj4GRfAmtQzcd1FjKE0sWu5IHXRUH9YxIQ== - dependencies: - deepmerge "^4.2.2" - he "^1.2.0" - htmlparser2 "^6.1.0" - minimist "^1.2.5" - -htmlparser2@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" - integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.0.0" - domutils "^2.5.2" - entities "^2.0.0" - -http-errors@^2.0.0, http-errors@~2.0.0, http-errors@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b" - integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ== - dependencies: - depd "~2.0.0" - inherits "~2.0.4" - setprototypeof "~1.2.0" - statuses "~2.0.2" - toidentifier "~1.0.1" - -http-terminator@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/http-terminator/-/http-terminator-3.2.0.tgz#bc158d2694b733ca4fbf22a35065a81a609fb3e9" - integrity sha512-JLjck1EzPaWjsmIf8bziM3p9fgR1Y3JoUKAkyYEbZmFrIvJM6I8vVJfBGWlEtV9IWOvzNnaTtjuwZeBY2kwB4g== - dependencies: - delay "^5.0.0" - p-wait-for "^3.2.0" - roarr "^7.0.4" - type-fest "^2.3.3" - -iconv-lite@~0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -ieee754@^1.1.13, ieee754@^1.1.4, ieee754@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - -inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -ini@~1.3.0: - version "1.3.8" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - -ioredis@^5.3.2: - version "5.10.0" - resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.10.0.tgz#a745ffec754e79435a60cad166076bd33c811aca" - integrity sha512-HVBe9OFuqs+Z6n64q09PQvP1/R4Bm+30PAyyD4wIEqssh3v9L21QjCVk4kRLucMBcDokJTcLjsGeVRlq/nH6DA== - dependencies: - "@ioredis/commands" "1.5.1" - cluster-key-slot "^1.1.0" - debug "^4.3.4" - denque "^2.1.0" - lodash.defaults "^4.2.0" - lodash.isarguments "^3.1.0" - redis-errors "^1.2.0" - redis-parser "^3.0.0" - standard-as-callback "^2.1.0" - -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -ipaddr.js@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.3.0.tgz#71dce70e1398122208996d1c22f2ba46a24b1abc" - integrity sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg== - -is-arrayish@^0.3.1: - version "0.3.4" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.4.tgz#1ee5553818511915685d33bb13d31bf854e5059d" - integrity sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -iso-datestring-validator@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz#2daa80d2900b7a954f9f731d42f96ee0c19a6895" - integrity sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA== - -jackspeak@^3.1.2: - version "3.4.3" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" - integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== - dependencies: - "@isaacs/cliui" "^8.0.2" - optionalDependencies: - "@pkgjs/parseargs" "^0.11.0" - -jose@^5.0.1, jose@^5.2.0: - version "5.10.0" - resolved "https://registry.yarnpkg.com/jose/-/jose-5.10.0.tgz#c37346a099d6467c401351a9a0c2161e0f52c4be" - integrity sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg== - -key-encoder@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/key-encoder/-/key-encoder-2.0.3.tgz#77073bb48ff1fe2173bb2088b83b91152c8fa4ba" - integrity sha512-fgBtpAGIr/Fy5/+ZLQZIPPhsZEcbSlYu/Wu96tNDFNSjSACw5lEIOFeaVdQ/iwrb8oxjlWi6wmWdH76hV6GZjg== - dependencies: - "@types/elliptic" "^6.4.9" - asn1.js "^5.0.1" - bn.js "^4.11.8" - elliptic "^6.4.1" - -kysely@^0.22.0: - version "0.22.0" - resolved "https://registry.yarnpkg.com/kysely/-/kysely-0.22.0.tgz#8aac53942da3cadc604d7d154a746d983fe8f7b9" - integrity sha512-ZE3qWtnqLOalodzfK5QUEcm7AEulhxsPNuKaGFsC3XiqO92vMLm+mAHk/NnbSIOtC4RmGm0nsv700i8KDp1gfQ== - -kysely@^0.23.4: - version "0.23.5" - resolved "https://registry.yarnpkg.com/kysely/-/kysely-0.23.5.tgz#60c63d94e1c42cc0411be8aaa688a0f27405f514" - integrity sha512-TH+b56pVXQq0tsyooYLeNfV11j6ih7D50dyN8tkM0e7ndiUH28Nziojiog3qRFlmEj9XePYdZUrNJ2079Qjdow== - -lande@^1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/lande/-/lande-1.0.10.tgz#1f6c6542e628338eb18def22edd1038f5fce9e7a" - integrity sha512-yT52DQh+UV2pEp08jOYrA4drDv0DbjpiRyZYgl25ak9G2cVR2AimzrqkYQWrD9a7Ud+qkAcaiDDoNH9DXfHPmw== - dependencies: - toygrad "^2.6.0" - -leo-profanity@^1.8.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/leo-profanity/-/leo-profanity-1.9.0.tgz#96acdd9373b231794e82010c763acc51bb53d60f" - integrity sha512-vMrrrjsbT+fA5I/1rlBEVT5YjJsw1ASIVF8/xBEdZ6ylsg5AEIBrEvHxwe5XAmfVuTBi7aw2KcO2L3s9ddpKzw== - optionalDependencies: - french-badwords-list "^1.0.7" - russian-bad-words "^0.5.0" - -lodash.camelcase@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" - integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== - -lodash.defaults@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" - integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== - -lodash.isarguments@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" - integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg== - -long@^5.0.0: - version "5.3.2" - resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83" - integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== - -lru-cache@^10.2.0: - version "10.4.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" - integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== - -make-error@^1.1.1: - version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - -math-intrinsics@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" - integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - -merge-descriptors@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" - integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -"mime-db@>= 1.43.0 < 2": - version "1.54.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" - integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== - -mime-types@^2.1.12, mime-types@^2.1.35, mime-types@~2.1.24, mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -mimic-response@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" - integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== - -minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - -minimalistic-crypto-utils@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" - integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== - -minimatch@^9.0.4: - version "9.0.9" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.9.tgz#9b0cb9fcb78087f6fd7eababe2511c4d3d60574e" - integrity sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg== - dependencies: - brace-expansion "^2.0.2" - -minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: - version "7.1.3" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.3.tgz#79389b4eb1bb2d003a9bba87d492f2bd37bdc65b" - integrity sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A== - -mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" - integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - -ms@2.1.3, ms@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -multiformats@^9.4.2, multiformats@^9.5.4, multiformats@^9.6.4, multiformats@^9.9.0: - version "9.9.0" - resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.9.0.tgz#c68354e7d21037a8f1f8833c8ccd68618e8f1d37" - integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg== - -murmurhash@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/murmurhash/-/murmurhash-2.0.1.tgz#4097720e08cf978872194ad84ea5be2dec9b610f" - integrity sha512-5vQEh3y+DG/lMPM0mCGPDnyV8chYg/g7rl6v3Gd8WMF9S429ox3Xk8qrk174kWhG767KQMqqxLD1WnGd77hiew== - -napi-build-utils@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz#13c22c0187fcfccce1461844136372a47ddc027e" - integrity sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA== - -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - -negotiator@~0.6.4: - version "0.6.4" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7" - integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w== - -neo-async@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - -node-abi@^3.3.0: - version "3.87.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.87.0.tgz#423e28fea5c2f195fddd98acded9938c001ae6dd" - integrity sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ== - dependencies: - semver "^7.3.5" - -nodemailer-html-to-text@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/nodemailer-html-to-text/-/nodemailer-html-to-text-3.2.0.tgz#91b959491fef8f7d91796047abb728aa86d4a12b" - integrity sha512-RJUC6640QV1PzTHHapOrc6IzrAJUZtk2BdVdINZ9VTLm+mcQNyBO9LYyhrnufkzqiD9l8hPLJ97rSyK4WanPNg== - dependencies: - html-to-text "7.1.1" - -nodemailer@^6.8.0: - version "6.10.1" - resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.10.1.tgz#cbc434c54238f83a51c07eabd04e2b3e832da623" - integrity sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA== - -object-assign@^4: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -object-inspect@^1.13.3: - version "1.13.4" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" - integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== - -on-exit-leak-free@^2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz#fed195c9ebddb7d9e4c3842f93f281ac8dadd3b8" - integrity sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA== - -on-finished@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -on-headers@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.1.0.tgz#59da4f91c45f5f989c6e4bcedc5a3b0aed70ff65" - integrity sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A== - -once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -one-webcrypto@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/one-webcrypto/-/one-webcrypto-1.0.3.tgz#f951243cde29b79b6745ad14966fc598a609997c" - integrity sha512-fu9ywBVBPx0gS9K0etIROTiCkvI5S1TDjFsYFb3rC1ewFxeOqsbzq7aIMBHsYfrTHBcGXJaONXXjTl8B01cW1Q== - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== - -p-queue@^6.6.2: - version "6.6.2" - resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" - integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== - dependencies: - eventemitter3 "^4.0.4" - p-timeout "^3.2.0" - -p-timeout@^3.0.0, p-timeout@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" - integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== - dependencies: - p-finally "^1.0.0" - -p-wait-for@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/p-wait-for/-/p-wait-for-3.2.0.tgz#640429bcabf3b0dd9f492c31539c5718cb6a3f1f" - integrity sha512-wpgERjNkLrBiFmkMEjuZJEWKKDrNfHCKA1OhyN1wg1FrLkULbviEy6py1AyJUgZ72YWFbZ38FIpnqvVqAlDUwA== - dependencies: - p-timeout "^3.0.0" - -package-json-from-dist@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" - integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== - -parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-scurry@^1.11.1: - version "1.11.1" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" - integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== - dependencies: - lru-cache "^10.2.0" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - -path-to-regexp@~0.1.12: - version "0.1.12" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" - integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== - -peek-readable@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-4.1.0.tgz#4ece1111bf5c2ad8867c314c81356847e8a62e72" - integrity sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg== - -pg-cloudflare@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz#386035d4bfcf1a7045b026f8b21acf5353f14d65" - integrity sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ== - -pg-connection-string@^2.12.0: - version "2.12.0" - resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.12.0.tgz#4084f917902bb2daae3dc1376fe24ac7b4eaccf2" - integrity sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ== - -pg-int8@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" - integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== - -pg-pool@^3.13.0: - version "3.13.0" - resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.13.0.tgz#416482e9700e8f80c685a6ae5681697a413c13a3" - integrity sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA== - -pg-protocol@^1.13.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.13.0.tgz#fdaf6d020bca590d58bb991b4b16fc448efe0511" - integrity sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w== - -pg-types@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" - integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== - dependencies: - pg-int8 "1.0.1" - postgres-array "~2.0.0" - postgres-bytea "~1.0.0" - postgres-date "~1.0.4" - postgres-interval "^1.1.0" - -pg@^8.10.0, pg@^8.9.0: - version "8.20.0" - resolved "https://registry.yarnpkg.com/pg/-/pg-8.20.0.tgz#1a274de944cb329fd6dd77a6d371a005ba6b136d" - integrity sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA== - dependencies: - pg-connection-string "^2.12.0" - pg-pool "^3.13.0" - pg-protocol "^1.13.0" - pg-types "2.2.0" - pgpass "1.0.5" - optionalDependencies: - pg-cloudflare "^1.3.0" - -pgpass@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" - integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== - dependencies: - split2 "^4.1.0" - -pino-abstract-transport@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz#97f9f2631931e242da531b5c66d3079c12c9d1b5" - integrity sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q== - dependencies: - readable-stream "^4.0.0" - split2 "^4.0.0" - -pino-http@^8.2.1, pino-http@^8.3.3: - version "8.6.1" - resolved "https://registry.yarnpkg.com/pino-http/-/pino-http-8.6.1.tgz#46338caea759c9c86fc44f3226ed6976364ec269" - integrity sha512-J0hiJgUExtBXP2BjrK4VB305tHXS31sCmWJ9XJo2wPkLHa1NFPuW4V9wjG27PAc2fmBCigiNhQKpvrx+kntBPA== - dependencies: - get-caller-file "^2.0.5" - pino "^8.17.1" - pino-std-serializers "^6.2.2" - process-warning "^3.0.0" - -pino-std-serializers@^6.0.0, pino-std-serializers@^6.2.2: - version "6.2.2" - resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz#d9a9b5f2b9a402486a5fc4db0a737570a860aab3" - integrity sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA== - -pino@^8.11.0, pino@^8.17.1, pino@^8.21.0, pino@^8.6.1: - version "8.21.0" - resolved "https://registry.yarnpkg.com/pino/-/pino-8.21.0.tgz#e1207f3675a2722940d62da79a7a55a98409f00d" - integrity sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q== - dependencies: - atomic-sleep "^1.0.0" - fast-redact "^3.1.1" - on-exit-leak-free "^2.1.0" - pino-abstract-transport "^1.2.0" - pino-std-serializers "^6.0.0" - process-warning "^3.0.0" - quick-format-unescaped "^4.0.3" - real-require "^0.2.0" - safe-stable-stringify "^2.3.1" - sonic-boom "^3.7.0" - thread-stream "^2.6.0" - -postgres-array@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" - integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== - -postgres-bytea@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.1.tgz#c40b3da0222c500ff1e51c5d7014b60b79697c7a" - integrity sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ== - -postgres-date@~1.0.4: - version "1.0.7" - resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" - integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== - -postgres-interval@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" - integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== - dependencies: - xtend "^4.0.0" - -prebuild-install@^7.1.1: - version "7.1.3" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.3.tgz#d630abad2b147443f20a212917beae68b8092eec" - integrity sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug== - dependencies: - detect-libc "^2.0.0" - expand-template "^2.0.3" - github-from-package "0.0.0" - minimist "^1.2.3" - mkdirp-classic "^0.5.3" - napi-build-utils "^2.0.0" - node-abi "^3.3.0" - pump "^3.0.0" - rc "^1.2.7" - simple-get "^4.0.0" - tar-fs "^2.0.0" - tunnel-agent "^0.6.0" - -process-warning@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-3.0.0.tgz#96e5b88884187a1dce6f5c3166d611132058710b" - integrity sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ== - -process@^0.11.10: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== - -protobufjs@^7.2.5, protobufjs@^7.5.3: - version "7.5.4" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.5.4.tgz#885d31fe9c4b37f25d1bb600da30b1c5b37d286a" - integrity sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg== - dependencies: - "@protobufjs/aspromise" "^1.1.2" - "@protobufjs/base64" "^1.1.2" - "@protobufjs/codegen" "^2.0.4" - "@protobufjs/eventemitter" "^1.1.0" - "@protobufjs/fetch" "^1.1.0" - "@protobufjs/float" "^1.0.2" - "@protobufjs/inquire" "^1.1.0" - "@protobufjs/path" "^1.1.2" - "@protobufjs/pool" "^1.1.0" - "@protobufjs/utf8" "^1.1.0" - "@types/node" ">=13.7.0" - long "^5.0.0" - -proxy-addr@~2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - -proxy-from-env@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== - -pump@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.4.tgz#1f313430527fa8b905622ebd22fe1444e757ab3c" - integrity sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -qs@~6.14.0: - version "6.14.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.2.tgz#b5634cf9d9ad9898e31fba3504e866e8efb6798c" - integrity sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q== - dependencies: - side-channel "^1.1.0" - -quick-format-unescaped@^4.0.3: - version "4.0.4" - resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" - integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== - -range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -rate-limiter-flexible@^2.4.1: - version "2.4.2" - resolved "https://registry.yarnpkg.com/rate-limiter-flexible/-/rate-limiter-flexible-2.4.2.tgz#2a219cc473f015142fd8fb599371223d730decbd" - integrity sha512-rMATGGOdO1suFyf/mI5LYhts71g1sbdhmd6YvdiXO2gJnd42Tt6QS4JUKJKSWVVkMtBacm6l40FR7Trjo6Iruw== - -raw-body@~2.5.3: - version "2.5.3" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.3.tgz#11c6650ee770a7de1b494f197927de0c923822e2" - integrity sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA== - dependencies: - bytes "~3.1.2" - http-errors "~2.0.1" - iconv-lite "~0.4.24" - unpipe "~1.0.0" - -rc@^1.2.7: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readable-stream@^4.0.0, readable-stream@^4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.7.0.tgz#cedbd8a1146c13dfff8dab14068028d58c15ac91" - integrity sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg== - dependencies: - abort-controller "^3.0.0" - buffer "^6.0.3" - events "^3.3.0" - process "^0.11.10" - string_decoder "^1.3.0" - -readable-web-to-node-stream@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz#392ba37707af5bf62d725c36c1b5d6ef4119eefc" - integrity sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw== - dependencies: - readable-stream "^4.7.0" - -real-require@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.2.0.tgz#209632dea1810be2ae063a6ac084fee7e33fba78" - integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg== - -redis-errors@^1.0.0, redis-errors@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" - integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w== - -redis-parser@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" - integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A== - dependencies: - redis-errors "^1.0.0" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -roarr@^7.0.4: - version "7.21.4" - resolved "https://registry.yarnpkg.com/roarr/-/roarr-7.21.4.tgz#c202c84d0b201ac821591f4bb46a3b28b8affe6b" - integrity sha512-qvfUKCrpPzhWmQ4NxRYnuwhkI5lwmObhBU06BCK/lpj6PID9nL4Hk6XDwek2foKI+TMaV+Yw//XZshGF2Lox/Q== - dependencies: - fast-printf "^1.6.9" - safe-stable-stringify "^2.4.3" - semver-compare "^1.0.0" - -russian-bad-words@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/russian-bad-words/-/russian-bad-words-0.5.0.tgz#d4abcdc55f7af37606cdbac44bcb9e0bde6c3855" - integrity sha512-euNvEYki6iYYpkNbeudW+lEMMYGEmN7EBwVF8ezlbv0bZoQpVYB7W10cCeUIGV7Ed50sJynLQ0c559q5iI0ejQ== - -rxjs@^7.5.2: - version "7.8.2" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b" - integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== - dependencies: - tslib "^2.1.0" - -safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-stable-stringify@^2.3.1, safe-stable-stringify@^2.4.3: - version "2.5.0" - resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz#4ca2f8e385f2831c432a719b108a3bf7af42a1dd" - integrity sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA== - -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -semver-compare@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" - integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow== - -semver@^7.3.5, semver@^7.6.3: - version "7.7.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a" - integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== - -send@~0.19.0, send@~0.19.1: - version "0.19.2" - resolved "https://registry.yarnpkg.com/send/-/send-0.19.2.tgz#59bc0da1b4ea7ad42736fd642b1c4294e114ff29" - integrity sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~2.0.0" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "~0.5.2" - http-errors "~2.0.1" - mime "1.6.0" - ms "2.1.3" - on-finished "~2.4.1" - range-parser "~1.2.1" - statuses "~2.0.2" - -serve-static@~1.16.2: - version "1.16.3" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.3.tgz#a97b74d955778583f3862a4f0b841eb4d5d78cf9" - integrity sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA== - dependencies: - encodeurl "~2.0.0" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "~0.19.1" - -setprototypeof@1.2.0, setprototypeof@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -sharp@^0.33.5: - version "0.33.5" - resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.33.5.tgz#13e0e4130cc309d6a9497596715240b2ec0c594e" - integrity sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw== - dependencies: - color "^4.2.3" - detect-libc "^2.0.3" - semver "^7.6.3" - optionalDependencies: - "@img/sharp-darwin-arm64" "0.33.5" - "@img/sharp-darwin-x64" "0.33.5" - "@img/sharp-libvips-darwin-arm64" "1.0.4" - "@img/sharp-libvips-darwin-x64" "1.0.4" - "@img/sharp-libvips-linux-arm" "1.0.5" - "@img/sharp-libvips-linux-arm64" "1.0.4" - "@img/sharp-libvips-linux-s390x" "1.0.4" - "@img/sharp-libvips-linux-x64" "1.0.4" - "@img/sharp-libvips-linuxmusl-arm64" "1.0.4" - "@img/sharp-libvips-linuxmusl-x64" "1.0.4" - "@img/sharp-linux-arm" "0.33.5" - "@img/sharp-linux-arm64" "0.33.5" - "@img/sharp-linux-s390x" "0.33.5" - "@img/sharp-linux-x64" "0.33.5" - "@img/sharp-linuxmusl-arm64" "0.33.5" - "@img/sharp-linuxmusl-x64" "0.33.5" - "@img/sharp-wasm32" "0.33.5" - "@img/sharp-win32-ia32" "0.33.5" - "@img/sharp-win32-x64" "0.33.5" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -side-channel-list@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" - integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== - dependencies: - es-errors "^1.3.0" - object-inspect "^1.13.3" - -side-channel-map@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" - integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - get-intrinsic "^1.2.5" - object-inspect "^1.13.3" - -side-channel-weakmap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" - integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - get-intrinsic "^1.2.5" - object-inspect "^1.13.3" - side-channel-map "^1.0.1" - -side-channel@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" - integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== - dependencies: - es-errors "^1.3.0" - object-inspect "^1.13.3" - side-channel-list "^1.0.0" - side-channel-map "^1.0.1" - side-channel-weakmap "^1.0.2" - -signal-exit@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" - integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== - -simple-concat@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" - integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== - -simple-get@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" - integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== - dependencies: - decompress-response "^6.0.0" - once "^1.3.1" - simple-concat "^1.0.0" - -simple-swizzle@^0.2.2: - version "0.2.4" - resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.4.tgz#a8d11a45a11600d6a1ecdff6363329e3648c3667" - integrity sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw== - dependencies: - is-arrayish "^0.3.1" - -sonic-boom@^3.7.0: - version "3.8.1" - resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-3.8.1.tgz#d5ba8c4e26d6176c9a1d14d549d9ff579a163422" - integrity sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg== - dependencies: - atomic-sleep "^1.0.0" - -source-map@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -split2@^4.0.0, split2@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" - integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== - -standard-as-callback@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" - integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== - -statuses@~2.0.1, statuses@~2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" - integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== - -stream-browserify@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f" - integrity sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA== - dependencies: - inherits "~2.0.4" - readable-stream "^3.5.0" - -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^5.0.1, string-width@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" - integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== - dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^9.2.2" - strip-ansi "^7.0.1" - -string_decoder@^1.1.1, string_decoder@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^7.0.1: - version "7.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.2.0.tgz#d22a269522836a627af8d04b5c3fd2c7fa3e32e3" - integrity sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w== - dependencies: - ansi-regex "^6.2.2" - -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== - -strnum@^2.1.2: - version "2.2.0" - resolved "https://registry.yarnpkg.com/strnum/-/strnum-2.2.0.tgz#8b582b637e4621f62ff714493e0ce30846f903a6" - integrity sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg== - -strtok3@^6.2.4: - version "6.3.0" - resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-6.3.0.tgz#358b80ffe6d5d5620e19a073aa78ce947a90f9a0" - integrity sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw== - dependencies: - "@tokenizer/token" "^0.3.0" - peek-readable "^4.1.0" - -structured-headers@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/structured-headers/-/structured-headers-1.0.1.tgz#1821e434e0fe45bdd78f07c779b16519ab520415" - integrity sha512-QYBxdBtA4Tl5rFPuqmbmdrS9kbtren74RTJTcs0VSQNVV5iRhJD4QlYTLD0+81SBwUQctjEQzjTRI3WG4DzICA== - -tar-fs@^2.0.0: - version "2.1.4" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.4.tgz#800824dbf4ef06ded9afea4acafe71c67c76b930" - integrity sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ== - dependencies: - chownr "^1.1.1" - mkdirp-classic "^0.5.2" - pump "^3.0.0" - tar-stream "^2.1.4" - -tar-stream@^2.1.4: - version "2.2.0" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" - integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== - dependencies: - bl "^4.0.3" - end-of-stream "^1.4.1" - fs-constants "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.1.1" - -thread-stream@^2.6.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.7.0.tgz#d8a8e1b3fd538a6cca8ce69dbe5d3d097b601e11" - integrity sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw== - dependencies: - real-require "^0.2.0" - -tlds@^1.234.0: - version "1.261.0" - resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.261.0.tgz#055e412e92f01f84a9c8ac0504d3472d68b3c4c9" - integrity sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA== - -toidentifier@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -token-types@^4.1.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/token-types/-/token-types-4.2.1.tgz#0f897f03665846982806e138977dbe72d44df753" - integrity sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ== - dependencies: - "@tokenizer/token" "^0.3.0" - ieee754 "^1.2.1" - -toygrad@^2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/toygrad/-/toygrad-2.6.0.tgz#e814bb7da026db8e08dc7da14c7155f49cdb4d54" - integrity sha512-g4zBmlSbvzOE5FOILxYkAybTSxijKLkj1WoNqVGnbMcWDyj4wWQ+eYSr3ik7XOpIgMq/7eBcPRTJX3DM2E0YMg== - -ts-node@^10.9.2: - version "10.9.2" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" - integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== - dependencies: - "@cspotcode/source-map-support" "^0.8.0" - "@tsconfig/node10" "^1.0.7" - "@tsconfig/node12" "^1.0.7" - "@tsconfig/node14" "^1.0.0" - "@tsconfig/node16" "^1.0.2" - acorn "^8.4.1" - acorn-walk "^8.1.1" - arg "^4.1.0" - create-require "^1.1.0" - diff "^4.0.1" - make-error "^1.1.1" - v8-compile-cache-lib "^3.0.1" - yn "3.1.1" - -tslib@^2.1.0, tslib@^2.4.0, tslib@^2.6.2, tslib@^2.8.1: - version "2.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" - integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== - -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== - dependencies: - safe-buffer "^5.0.1" - -type-fest@^2.3.3: - version "2.19.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" - integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== - -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -typed-emitter@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/typed-emitter/-/typed-emitter-2.1.0.tgz#ca78e3d8ef1476f228f548d62e04e3d4d3fd77fb" - integrity sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA== - optionalDependencies: - rxjs "^7.5.2" - -typescript@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-6.0.2.tgz#0b1bfb15f68c64b97032f3d78abbf98bdbba501f" - integrity sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ== - -uglify-js@^3.1.4: - version "3.19.3" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f" - integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== - -uint8arrays@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/uint8arrays/-/uint8arrays-3.0.0.tgz#260869efb8422418b6f04e3fac73a3908175c63b" - integrity sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA== - dependencies: - multiformats "^9.4.2" - -undici-types@~7.18.0: - version "7.18.2" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.18.2.tgz#29357a89e7b7ca4aef3bf0fd3fd0cd73884229e9" - integrity sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w== - -undici@^5.28.4: - version "5.29.0" - resolved "https://registry.yarnpkg.com/undici/-/undici-5.29.0.tgz#419595449ae3f2cdcba3580a2e8903399bd1f5a3" - integrity sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg== - dependencies: - "@fastify/busboy" "^2.0.0" - -undici@^6.14.1, undici@^6.19.8: - version "6.23.0" - resolved "https://registry.yarnpkg.com/undici/-/undici-6.23.0.tgz#7953087744d9095a96f115de3140ca3828aff3a4" - integrity sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g== - -unicode-segmenter@^0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/unicode-segmenter/-/unicode-segmenter-0.14.5.tgz#c658f6dd30de172cdcd94542adc205ba43fb63c6" - integrity sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g== - -unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - -util-deprecate@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - -v8-compile-cache-lib@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" - integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== - -varint@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/varint/-/varint-6.0.0.tgz#9881eb0ce8feaea6512439d19ddf84bf551661d0" - integrity sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg== - -vary@^1, vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -wordwrap@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" - integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== - -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" - integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== - dependencies: - ansi-styles "^6.1.0" - string-width "^5.0.1" - strip-ansi "^7.0.1" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -ws@^8.12.0: - version "8.19.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.19.0.tgz#ddc2bdfa5b9ad860204f5a72a4863a8895fd8c8b" - integrity sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg== - -xtend@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yargs-parser@^21.1.1: - version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs@^17.7.2: - version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" - integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== - dependencies: - cliui "^8.0.1" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.1.1" - -yn@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" - integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== - -zod@3.23.8: - version "3.23.8" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" - integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== - -zod@^3.14.2, zod@^3.23.8: - version "3.25.76" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34" - integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ== diff --git a/docs/build.md b/docs/build.md index 48eea5aecd..436a1197d0 100644 --- a/docs/build.md +++ b/docs/build.md @@ -2,8 +2,8 @@ ## Running Web App -- `yarn` -- `yarn web` +- `pnpm install` +- `pnpm web` You're all set! @@ -32,11 +32,11 @@ This is NOT required when developing for web. - `bundler install` (this will install Cocoapods) - After initial setup: - Copy `google-services.json.example` to `google-services.json` or provide your own `google-services.json`. (A real firebase project is NOT required) - - `npx expo prebuild` -> you will also need to run this anytime `app.json` or native `package.json` deps change + - `pnpm prebuild` -> you will also need to run this anytime `app.json` or native `package.json` deps change ### Running the Native App -- iOS: `yarn ios` +- iOS: `pnpm ios` - Xcode must be installed for this to run. - A simulator must be preconfigured in Xcode settings. - if no iOS versions are available, install the iOS runtime at `Xcode > Settings > Platforms`. @@ -48,24 +48,24 @@ This is NOT required when developing for web. - In addition, ensure Xcode Command Line Tools are installed using `xcode-select --install`. - Expo will require you to configure Xcode Signing. Follow the linked instructions. Error messages in Xcode related to the signing process can be safely ignored when installing on the iOS Simulator; Expo merely requires the profile to exist in order to install the app on the Simulator. - Make sure you do have a certificate: open Xcode > Settings > Accounts > (sign-in) > Manage Certificates > + > Apple Development > Done. - - If you still encounter issues, try `rm -rf ios` before trying to build again (`yarn ios`) -- Android: `yarn android` + - If you still encounter issues, try `rm -rf ios` before trying to build again (`pnpm ios`) +- Android: `pnpm android` - Install "Android Studio" - Make sure you have the Android SDK installed (Android Studio > Tools > Android SDK). - In "SDK Platforms": "Android x" (where x is Android's current version). - In "SDK Tools": "Android SDK Build-Tools" and "Android Emulator" are required. - Add `export ANDROID_HOME=/Users//Library/Android/sdk` to your `.zshrc` or `.bashrc` (and restart your terminal). - Setup an emulator (Android Studio > Tools > Device Manager). -- Web: `yarn web` (see the top of this file). +- Web: `pnpm web` (see the top of this file). -After you do `yarn ios` and `yarn android` once, you can later just run `yarn web` and then press either `i` or `a` to open iOS and Android emulators respectively which is much faster. However, if you make native changes, you'll have to do `yarn prebuild -p ios` and `yarn prebuild -p android` and then `yarn ios` and `yarn android` again before you can continue with the same workflow. +After you do `pnpm ios` and `pnpm android` once, you can later just run `pnpm web` and then press either `i` or `a` to open iOS and Android emulators respectively which is much faster. However, if you make native changes, you'll have to do `pnpm prebuild -p ios` and `pnpm prebuild -p android` and then `pnpm ios` and `pnpm android` again before you can continue with the same workflow. ### Tips - Copy the `.env.example` to `.env` and fill in any necessary tokens. (The Sentry token is NOT required; see instructions below if you want to enable Sentry.) -- To run on the device, add `--device` to the command (e.g. `yarn android --device`). To build in production mode (slower build, faster app), also add `--variant release` on Android or `--configuration Release` on iOS. +- To run on the device, add `--device` to the command (e.g. `pnpm android --device`). To build in production mode (slower build, faster app), also add `--variant release` on Android or `--configuration Release` on iOS. - If you want to use Expo EAS on your own builds without ejecting from Expo, make sure to change the `owner` and `extra.eas.projectId` properties. If you do not have an Expo account, you may remove these properties. -- `npx react-native info` Checks what has been installed. +- `pnpm exec react-native info` Checks what has been installed. - If the Android simulator frequently hangs or is very sluggish, [bump its memory limit](https://stackoverflow.com/a/40068396) - The Android simulator won't be able to access localhost services unless you run `adb reverse tcp:{PORT} tcp:{PORT}` - For instance, the locally-hosted dev-wallet will need `adb reverse tcp:3001 tcp:3001` @@ -74,10 +74,10 @@ After you do `yarn ios` and `yarn android` once, you can later just run `yarn we ### Running E2E Tests - Start in various console tabs: - - `yarn e2e:mock-server` - - `yarn e2e:start` -- Run once: `yarn e2e:build` -- Each test run: `yarn e2e:run` + - `pnpm e2e:mock-server` + - `pnpm e2e:start` +- Run once: `pnpm e2e:build` +- Each test run: `pnpm e2e:run` ### Adding Sentry @@ -85,7 +85,7 @@ Adding Sentry is NOT required. You can keep `SENTRY_AUTH_TOKEN=` in `.env` which However, if you're a part of the Bluesky team and want to enable Sentry, fill in `SENTRY_AUTH_TOKEN` in your `.env`. It can be created on the Sentry dashboard using [these instructions](https://docs.expo.dev/guides/using-sentry/#sign-up-for-a-sentry-account-and-create-a-project). -If you change `SENTRY_AUTH_TOKEN`, you need to do `yarn prebuild` before running `yarn ios` or `yarn android` again. +If you change `SENTRY_AUTH_TOKEN`, you need to do `pnpm prebuild` before running `pnpm ios` or `pnpm android` again. ### Adding bitdrift @@ -95,7 +95,7 @@ However, if you're a part of the Bluesky team and want to enable bitdrift, fill ### Adding and Updating Locales -- `yarn intl:build` -> you will also need to run this anytime `./src/locale/{locale}/messages.po` change +- `pnpm intl:build` -> you will also need to run this anytime `./src/locale/{locale}/messages.po` change ## Running the Backend Locally @@ -112,7 +112,7 @@ This is NOT required for app development but if you also want to develop the Blu - Launch a Postgres database on port 5432 - `cd packages/dev-env && pnpm start` -Run the account with the AppView proxy DID passed in as an environment variable: `EXPO_PUBLIC_BLUESKY_PROXY_DID=did:plc:dw4kbjf5mn7nhenabiqpkyh3 yarn start` +Run the account with the AppView proxy DID passed in as an environment variable: `EXPO_PUBLIC_BLUESKY_PROXY_DID=did:plc:dw4kbjf5mn7nhenabiqpkyh3 pnpm start` Then, when logging in or creating an account, point it to the localhost port of the devserver. @@ -123,7 +123,7 @@ The Go server in this repository is only used for serving the web app in product ### Prerequisites - [Go](https://go.dev/) -- [Yarn](https://yarnpkg.com/) +- [pnpm](https://pnpm.io/) ### Steps @@ -131,7 +131,7 @@ To run the build with Go, use staging credentials, your own, or any other accoun ``` cd social-app -yarn && yarn build-web +pnpm install && pnpm build-web cd bskyweb/ go mod tidy go build -v -tags timetzdata -o bskyweb ./cmd/bskyweb diff --git a/docs/deploy-ota.md b/docs/deploy-ota.md index e75ac1b3a3..fce864c761 100644 --- a/docs/deploy-ota.md +++ b/docs/deploy-ota.md @@ -57,6 +57,9 @@ release or OTA. Log in to the EAS CLI with `eas login` and manually set the build numbers to the values you found in **Step 1**. +> [!NOTE] +> If you don’t already have the EAS CLI, you can install it with `pnpm add -g eas-cli`. + ```sh > npx eas build:version:set -p ios > @@ -114,7 +117,7 @@ To confirm this, as mentioned above, you must completely clear the TestFlight build from your device and re-install from the App Store. Then, you'll need to: - Launch the app (or quit and reopen) and wait ~15s for the download to complete - Quit and reopen the app -- Check the `Settings > About` page and confirm the has matches the most recent has on your OTA branch +- Check the `Settings > About` page and confirm the hash matches the most recent hash on your OTA branch ### 9. Reset build numbers diff --git a/docs/localization.md b/docs/localization.md index f7e51ff359..43a5abeb30 100644 --- a/docs/localization.md +++ b/docs/localization.md @@ -42,20 +42,20 @@ Install the [Crowdin CLI](https://crowdin.github.io/crowdin-cli/). You will need ### English source-file sync with Crowdin -Every night, a GitHub action will run `yarn intl:extract` to update the english `messages.po` file. This will be automatically synced with Crowdin. Crowdin should notify all subscribed users of new translations. +Every night, a GitHub action will run `pnpm intl:extract` to update the english `messages.po` file. This will be automatically synced with Crowdin. Crowdin should notify all subscribed users of new translations. ### Release process 1. Pull main and create a branch. -1. Run `yarn intl:release` to fetch all translation updates from Crowdin and extract all `.po` files so that they're synced with the latest code. Commit that. +1. Run `pnpm intl:release` to fetch all translation updates from Crowdin and extract all `.po` files so that they're synced with the latest code. Commit that. 1. Create a PR, ensure the translations all look correct, and merge. 1. If needed: 1. Merge all approved translation PRs (contributions from outside crowdin). - 1. Run `yarn intl:push` to sync Crowdin with the state of the repo. + 1. Run `pnpm intl:push` to sync Crowdin with the state of the repo. ### Testing the translations in Crowdin -You can run `yarn intl:pull` to pull the currently-approved translations from Crowdin. +You can run `pnpm intl:pull` to pull the currently-approved translations from Crowdin. ## Developers @@ -118,15 +118,15 @@ function sayHello() { } ``` -We can then run `yarn intl:extract` to update the catalog in `src/locale/locales/{locale}/messages.po`. This will add the new string to the catalog. -We can then run `yarn intl:compile` to update the translation files in `src/locale/locales/{locale}/messages.js`. This will add the new string to the translation files. +We can then run `pnpm intl:extract` to update the catalog in `src/locale/locales/{locale}/messages.po`. This will add the new string to the catalog. +We can then run `pnpm intl:compile` to update the translation files in `src/locale/locales/{locale}/messages.js`. This will add the new string to the translation files. The configuration for translations is defined in `lingui.config.js` So the workflow is as follows: 1. Wrap messages in Trans macro -2. Run `yarn intl:extract` command to generate message catalogs +2. Run `pnpm intl:extract` command to generate message catalogs 3. Translate message catalogs (send them to translators usually) -4. Run `yarn intl:compile` to create runtime catalogs +4. Run `pnpm intl:compile` to create runtime catalogs 5. Load runtime catalog 6. Enjoy translated app! diff --git a/docs/testing.md b/docs/testing.md index bc4a6c8198..ae127d3194 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -25,13 +25,13 @@ adb reverse tcp:3000 tcp:3000 ### Running Maestro tests -- In one tab, run `yarn e2e:mock-server` -- In a second tab, run `yarn e2e:build` -- In a third tab, run `yarn e2e:run` +- In one tab, run `pnpm e2e:mock-server` +- In a second tab, run `pnpm e2e:build` +- In a third tab, run `pnpm e2e:run` ## Using Flashlight for Performance Testing 1. Make sure Maestro is installed (optional: only for automated testing) by following the instructions above 2. Install Flashlight by following [these instructions](https://docs.flashlight.dev/) -3. The simplest way to get started is by running `yarn perf:measure` which will run a live preview of the performance test results. You can [see a demo here](https://github.com/bamlab/flashlight/assets/4534323/4038a342-f145-4c3b-8cde-17949bf52612) -4. The `yarn perf:test:measure` will run the `scroll.yaml` test located in `__e2e__/maestro/scroll.yaml` and give the results in `.perf/results.json` which can be viewed by running `yarn:perf:results` -5. You can also run your own tests by running `yarn perf:test ` where `` is the path to your test file. For example, `yarn perf:test __e2e__/maestro/scroll.yaml` will run the `scroll.yaml` test located in `__e2e__/maestro/scroll.yaml`. +3. The simplest way to get started is by running `pnpm perf:measure` which will run a live preview of the performance test results. You can [see a demo here](https://github.com/bamlab/flashlight/assets/4534323/4038a342-f145-4c3b-8cde-17949bf52612) +4. The `pnpm perf:test:measure` will run the `scroll.yaml` test located in `__e2e__/maestro/scroll.yaml` and give the results in `.perf/results.json` which can be viewed by running `pnpm perf:results` +5. You can also run your own tests by running `pnpm perf:test ` where `` is the path to your test file. For example, `pnpm perf:test __e2e__/maestro/scroll.yaml` will run the `scroll.yaml` test located in `__e2e__/maestro/scroll.yaml`. diff --git a/eas.json b/eas.json index 5b7961af48..83549039cd 100644 --- a/eas.json +++ b/eas.json @@ -1,12 +1,12 @@ { "cli": { - "version": ">= 3.8.1", + "version": ">=18", "promptToConfigurePushNotifications": false, "appVersionSource": "remote" }, "build": { "base": { - "node": "20.19.4" + "node": "24.15.0" }, "development": { "extends": "base", diff --git a/eslint-suppressions.json b/eslint-suppressions.json index f192be24e6..c3bbd462ca 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -1,12 +1,173 @@ { - "src/alf/util/flatten.ts": { + "modules/bottom-sheet/src/BottomSheetNativeComponent.tsx": { "@typescript-eslint/no-explicit-any": { + "count": 2 + }, + "@typescript-eslint/no-unsafe-call": { + "count": 2 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 2 + } + }, + "modules/bottom-sheet/src/BottomSheetPortal.tsx": { + "no-restricted-imports": { "count": 1 } }, - "src/analytics/PassiveAnalytics.tsx": { + "modules/bottom-sheet/src/lib/Portal.tsx": { + "no-restricted-imports": { + "count": 1 + } + }, + "modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + }, + "no-restricted-imports": { + "count": 1 + } + }, + "modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandlerModule.web.ts": { + "@typescript-eslint/require-await": { + "count": 4 + } + }, + "modules/expo-bluesky-gif-view/src/GifView.tsx": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + }, + "@typescript-eslint/no-unsafe-call": { + "count": 4 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 4 + }, + "no-restricted-imports": { + "count": 1 + } + }, + "modules/expo-bluesky-gif-view/src/GifView.web.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 2 + }, + "@typescript-eslint/require-await": { + "count": 2 + } + }, + "modules/expo-bluesky-swiss-army/src/PlatformInfo/index.native.ts": { + "@typescript-eslint/no-unsafe-call": { + "count": 3 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 3 + } + }, + "modules/expo-bluesky-swiss-army/src/Referrer/index.android.ts": { + "@typescript-eslint/no-unsafe-call": { + "count": 2 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 2 + } + }, + "modules/expo-bluesky-swiss-army/src/SharedPrefs/index.native.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unsafe-call": { + "count": 9 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 9 + } + }, + "modules/expo-bluesky-swiss-army/src/VisibilityView/index.native.tsx": { + "@typescript-eslint/no-unsafe-call": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 + }, + "no-restricted-imports": { + "count": 1 + } + }, + "modules/expo-bluesky-swiss-army/src/VisibilityView/index.tsx": { + "@typescript-eslint/require-await": { + "count": 1 + } + }, + "modules/expo-bluesky-swiss-army/src/VisibilityView/types.ts": { + "no-restricted-imports": { + "count": 1 + } + }, + "modules/expo-emoji-picker/src/EmojiPickerView.tsx": { + "no-restricted-imports": { + "count": 1 + } + }, + "src/Navigation.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/Splash.android.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/Splash.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/ageAssurance/components/RedirectOverlay.tsx": { + "@typescript-eslint/require-await": { + "count": 1 + } + }, + "src/alf/util/flatten.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 9 + } + }, + "src/alf/util/systemUI.ts": { + "@typescript-eslint/no-floating-promises": { + "count": 2 + } + }, + "src/alf/util/useGutters.ts": { + "react-hooks/immutability": { + "count": 5 + } + }, + "src/analytics/PassiveAnalytics.tsx": { + "react-hooks/purity": { + "count": 1 + } + }, + "src/analytics/features/index.ts": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 + }, + "@typescript-eslint/require-await": { + "count": 1 + } + }, + "src/analytics/identifiers/session.test.ts": { + "@typescript-eslint/no-unsafe-call": { + "count": 9 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 9 } }, "src/analytics/metadata.ts": { @@ -17,26 +178,79 @@ "src/analytics/metrics/client.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 8 + }, + "@typescript-eslint/require-await": { + "count": 5 } }, "src/analytics/metrics/client.ts": { "@typescript-eslint/no-explicit-any": { "count": 5 + }, + "@typescript-eslint/no-floating-promises": { + "count": 2 + }, + "@typescript-eslint/no-unsafe-call": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 } }, - "src/analytics/metrics/types.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 + "src/analytics/utils.ts": { + "react-hooks/immutability": { + "count": 1 } }, - "src/components/Button.tsx": { - "@typescript-eslint/no-explicit-any": { - "count": 2 + "src/components/AppLanguageDropdown.tsx": { + "@typescript-eslint/no-unsafe-enum-comparison": { + "count": 1 + } + }, + "src/components/Autocomplete/Autocomplete.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/components/Autocomplete/useAutocomplete/index.ts": { + "react-hooks/immutability": { + "count": 1 + } + }, + "src/components/BotAccountAlert.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 } }, "src/components/Composer/index.tsx": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-floating-promises": { + "count": 1 + }, + "@typescript-eslint/no-redundant-type-constituents": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-call": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 7 + }, + "react-hooks/immutability": { + "count": 1 + }, + "react-hooks/refs": { + "count": 1 + } + }, + "src/components/DebugFieldDisplay.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 } }, "src/components/Dialog/index.tsx": { @@ -47,6 +261,18 @@ "src/components/Dialog/index.web.tsx": { "@typescript-eslint/no-explicit-any": { "count": 4 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-call": { + "count": 2 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 3 + }, + "@typescript-eslint/require-await": { + "count": 1 } }, "src/components/DraggableList/index.web.tsx": { @@ -54,6 +280,11 @@ "count": 2 } }, + "src/components/EmojiPicker/preload.web.ts": { + "@typescript-eslint/no-floating-promises": { + "count": 2 + } + }, "src/components/FeedCard.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 @@ -62,25 +293,52 @@ "src/components/FocusScope/index.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 + }, + "react-hooks/refs": { + "count": 1 } }, "src/components/InternationalPhoneCodeSelect.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 } }, - "src/components/Layout/const.ts": { - "@typescript-eslint/no-explicit-any": { + "src/components/KnownFollowers.tsx": { + "react-hooks/refs": { + "count": 6 + } + }, + "src/components/Lightbox/Lightbox.web.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 1 + } + }, + "src/components/Lightbox/pager/ImageItem/ImageItem.android.tsx": { + "react-hooks/immutability": { + "count": 1 + } + }, + "src/components/Lightbox/pager/ImageItem/ImageItem.ios.tsx": { + "react-hooks/refs": { + "count": 1 + } + }, + "src/components/LikedByList.tsx": { + "@typescript-eslint/no-misused-promises": { "count": 2 } }, "src/components/Lists.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 - } - }, - "src/components/Menu/index.web.tsx": { - "@typescript-eslint/no-explicit-any": { + }, + "@typescript-eslint/no-misused-promises": { "count": 1 } }, @@ -94,34 +352,119 @@ "count": 1 } }, + "src/components/Post/Embed/ExternalEmbed/ExternalGif.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 3 + } + }, "src/components/Post/Embed/ImageEmbed.tsx": { "@typescript-eslint/no-explicit-any": { + "count": 2 + }, + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 2 + }, + "@typescript-eslint/no-unsafe-enum-comparison": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 + }, + "react-hooks/immutability": { "count": 1 } }, "src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/utils.tsx": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-call": { + "count": 3 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 3 } }, "src/components/PostControls/BookmarkButton.tsx": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-misused-promises": { + "count": 2 + } + }, + "src/components/PostControls/DiscoverDebug.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/components/PostControls/ShareMenu/ShareMenuItems.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 3 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 + } + }, + "src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 3 + } + }, + "src/components/PostControls/ShareMenu/index.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 } }, "src/components/ProfileHoverCard/index.web.tsx": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-floating-promises": { + "count": 3 + }, + "@typescript-eslint/require-await": { + "count": 1 + }, + "react-hooks/immutability": { + "count": 1 + }, + "react-hooks/refs": { + "count": 2 + } + }, + "src/components/ProgressGuide/FollowDialog.tsx": { + "react-hooks/refs": { + "count": 2 + } + }, + "src/components/RichTextTag.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 2 } }, "src/components/Select/index.tsx": { "@typescript-eslint/no-explicit-any": { "count": 4 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 2 } }, "src/components/Select/index.web.tsx": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 } }, "src/components/Select/types.ts": { @@ -129,9 +472,44 @@ "count": 3 } }, + "src/components/SendErrorReportDialog.tsx": { + "@typescript-eslint/require-await": { + "count": 1 + } + }, + "src/components/StarterPack/Main/ProfilesList.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 1 + } + }, "src/components/StarterPack/ProfileStarterPacks.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-misused-promises": { + "count": 2 + } + }, + "src/components/StarterPack/QrCodeDialog.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 5 + }, + "@typescript-eslint/no-misused-promises": { + "count": 2 + }, + "@typescript-eslint/require-await": { + "count": 4 + } + }, + "src/components/StarterPack/ShareDialog.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + }, + "@typescript-eslint/no-misused-promises": { + "count": 2 + }, + "@typescript-eslint/require-await": { + "count": 1 } }, "src/components/StarterPack/Wizard/WizardEditListDialog.tsx": { @@ -139,44 +517,205 @@ "count": 1 } }, + "src/components/Tooltip/index.tsx": { + "@typescript-eslint/no-base-to-string": { + "count": 1 + } + }, + "src/components/Tooltip/index.web.tsx": { + "@typescript-eslint/no-base-to-string": { + "count": 1 + } + }, + "src/components/WhoCanReply.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + }, + "react-hooks/refs": { + "count": 1 + } + }, + "src/components/ageAssurance/AgeAssuranceErrors.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 1 + } + }, + "src/components/ageAssurance/AgeAssuranceInitDialog.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 2 + }, + "react-hooks/purity": { + "count": 1 + } + }, + "src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx": { + "@typescript-eslint/require-await": { + "count": 1 + } + }, + "src/components/contacts/components/OTPInput.tsx": { + "react-hooks/refs": { + "count": 1 + } + }, + "src/components/contacts/screens/GetContacts.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/components/contacts/screens/VerifyNumber.tsx": { + "@typescript-eslint/require-await": { + "count": 1 + }, + "react-hooks/purity": { + "count": 1 + } + }, + "src/components/contacts/screens/ViewMatches.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 1 + } + }, "src/components/dialogs/DeviceLocationRequestDialog.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 2 } }, "src/components/dialogs/EmailDialog/components/ResendEmailText.tsx": { - "@typescript-eslint/no-explicit-any": { + "@typescript-eslint/no-floating-promises": { "count": 1 } }, - "src/components/dialogs/GifSelect.tsx": { - "@typescript-eslint/no-explicit-any": { + "src/components/dialogs/EmailDialog/data/useAccountEmailState.ts": { + "react-hooks/set-state-in-effect": { "count": 1 } }, + "src/components/dialogs/EmailDialog/screens/Manage2FA/Disable.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 3 + } + }, + "src/components/dialogs/EmailDialog/screens/Manage2FA/Enable.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 1 + } + }, + "src/components/dialogs/EmailDialog/screens/Update.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 3 + } + }, + "src/components/dialogs/EmailDialog/screens/Verify.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 3 + } + }, "src/components/dialogs/LanguageSelectDialog.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 } }, + "src/components/dialogs/LinkWarning.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, "src/components/dialogs/MutedWords.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-floating-promises": { + "count": 2 + }, + "@typescript-eslint/no-misused-promises": { + "count": 3 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 2 + }, + "@typescript-eslint/require-await": { + "count": 1 } }, "src/components/dialogs/PostInteractionSettingsDialog.tsx": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 2 + }, + "react-hooks/preserve-manual-memoization": { + "count": 1 + } + }, + "src/components/dialogs/ServerInput.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/components/dialogs/StarterPackDialog.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 1 + } + }, + "src/components/dialogs/SwitchAccount.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 2 } }, "src/components/dialogs/lists/CreateOrEditListDialog.tsx": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 + } + }, + "src/components/dialogs/nuxs/FindContactsAnnouncement.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/components/dialogs/nuxs/index.tsx": { + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 + }, + "react-hooks/immutability": { + "count": 1 + }, + "react-hooks/set-state-in-effect": { + "count": 1 } }, "src/components/forms/DateField/index.web.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unsafe-call": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 2 + } + }, + "src/components/forms/SearchInput.tsx": { + "react-hooks/refs": { + "count": 1 } }, "src/components/forms/SegmentedControl.tsx": { @@ -184,6 +723,14 @@ "count": 1 } }, + "src/components/forms/TextField.tsx": { + "@typescript-eslint/no-unsafe-member-access": { + "count": 3 + }, + "react-hooks/refs": { + "count": 1 + } + }, "src/components/forms/ToggleButton.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 @@ -192,16 +739,48 @@ "src/components/hooks/useFollowMethods.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 2 + } + }, + "src/components/hooks/useLandingEntry.native.ts": { + "react-hooks/set-state-in-effect": { + "count": 1 + } + }, + "src/components/hooks/useLandingEntry.ts": { + "react-hooks/set-state-in-effect": { + "count": 1 + } + }, + "src/components/hooks/useRefreshOnFocus.ts": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/components/hooks/useRichText.ts": { + "@typescript-eslint/no-floating-promises": { + "count": 1 } }, "src/components/images/AutoSizedImage.tsx": { "@typescript-eslint/no-explicit-any": { - "count": 1 + "count": 2 } }, "src/components/images/Gallery/index.tsx": { "@typescript-eslint/no-explicit-any": { "count": 5 + }, + "@typescript-eslint/no-unsafe-call": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 4 + }, + "react-hooks/refs": { + "count": 1 } }, "src/components/images/Gallery/useKeyboardHandlers.ts": { @@ -224,8 +803,43 @@ "count": 2 } }, - "src/components/moderation/ReportDialog/index.tsx": { - "@typescript-eslint/no-explicit-any": { + "src/components/intents/VerifyEmailIntentDialog.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 1 + } + }, + "src/components/interstitials/TrendingVideos.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/components/moderation/ContentHider.tsx": { + "react-hooks/immutability": { + "count": 1 + } + }, + "src/components/moderation/LabelsOnMeDialog.tsx": { + "react-hooks/purity": { + "count": 1 + } + }, + "src/components/moderation/ModerationDetailsDialog.tsx": { + "react-hooks/purity": { + "count": 1 + } + }, + "src/components/verification/VerificationCreatePrompt.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 1 + } + }, + "src/components/verification/VerificationRemovePrompt.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 1 + } + }, + "src/features/liveEvents/preferences.ts": { + "@typescript-eslint/no-floating-promises": { "count": 1 } }, @@ -237,6 +851,12 @@ "src/geolocation/service.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 2 } }, "src/lib/ScrollContext.tsx": { @@ -244,9 +864,14 @@ "count": 3 } }, - "src/lib/api/index.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 5 + "src/lib/api/feed/demo.ts": { + "@typescript-eslint/require-await": { + "count": 2 + } + }, + "src/lib/api/feed/posts.ts": { + "@typescript-eslint/require-await": { + "count": 1 } }, "src/lib/async/retry.ts": { @@ -264,9 +889,20 @@ "count": 1 } }, + "src/lib/custom-animations/CountWheel.web.tsx": { + "react-hooks/set-state-in-effect": { + "count": 1 + } + }, "src/lib/functions.ts": { "@typescript-eslint/no-explicit-any": { "count": 6 + }, + "@typescript-eslint/no-unsafe-call": { + "count": 2 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 25 } }, "src/lib/getUserDisplayName.ts": { @@ -274,24 +910,72 @@ "count": 1 } }, - "src/lib/hooks/useAccountSwitcher.ts": { - "@typescript-eslint/no-explicit-any": { + "src/lib/haptics.ts": { + "@typescript-eslint/no-floating-promises": { "count": 1 } }, - "src/lib/hooks/useCleanError.ts": { + "src/lib/hooks/useAccountSwitcher.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 + } + }, + "src/lib/hooks/useAnimatedValue.ts": { + "react-hooks/refs": { + "count": 1 + } + }, + "src/lib/hooks/useDraggableScrollView.ts": { + "react-hooks/refs": { + "count": 1 + } + }, + "src/lib/hooks/useIntentHandler.ts": { + "@typescript-eslint/no-floating-promises": { + "count": 2 } }, "src/lib/hooks/useNonReactiveCallback.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unsafe-call": { + "count": 1 + } + }, + "src/lib/hooks/useNotificationHandler.ts": { + "@typescript-eslint/no-floating-promises": { + "count": 16 + }, + "@typescript-eslint/require-await": { + "count": 1 } }, "src/lib/hooks/useOTAUpdates.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-floating-promises": { + "count": 1 + }, + "@typescript-eslint/no-misused-promises": { + "count": 4 + } + }, + "src/lib/hooks/usePermissions.ts": { + "@typescript-eslint/no-misused-promises": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-enum-comparison": { + "count": 5 + } + }, + "src/lib/hooks/usePermissions.web.ts": { + "@typescript-eslint/require-await": { + "count": 3 } }, "src/lib/hooks/useRequireEmailVerification.tsx": { @@ -299,14 +983,33 @@ "count": 2 } }, + "src/lib/hooks/useTLDs.ts": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/lib/hooks/useTabFocusEffect.ts": { + "react-hooks/set-state-in-effect": { + "count": 1 + } + }, "src/lib/hooks/useToggleMutationQueue.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-floating-promises": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-call": { + "count": 2 } }, "src/lib/hooks/useWebScrollRestoration.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "react-hooks/immutability": { + "count": 1 } }, "src/lib/international-telephone-codes.ts": { @@ -314,9 +1017,30 @@ "count": 1 } }, + "src/lib/link-meta/link-meta.ts": { + "@typescript-eslint/no-unsafe-member-access": { + "count": 9 + } + }, "src/lib/media/manip.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-floating-promises": { + "count": 5 + } + }, + "src/lib/media/manip.web.ts": { + "@typescript-eslint/prefer-promise-reject-errors": { + "count": 1 + }, + "@typescript-eslint/require-await": { + "count": 3 + } + }, + "src/lib/media/picker.web.tsx": { + "@typescript-eslint/require-await": { + "count": 2 } }, "src/lib/media/save-image.ios.ts": { @@ -334,9 +1058,34 @@ "count": 1 } }, + "src/lib/notifications/notifications.ts": { + "@typescript-eslint/no-floating-promises": { + "count": 5 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-enum-comparison": { + "count": 2 + }, + "@typescript-eslint/require-await": { + "count": 1 + } + }, "src/lib/react-query.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 + }, + "react-hooks/refs": { + "count": 1 + } + }, + "src/lib/routes/links.ts": { + "@typescript-eslint/no-redundant-type-constituents": { + "count": 1 } }, "src/lib/routes/router.ts": { @@ -349,89 +1098,298 @@ "count": 1 } }, + "src/lib/sharing.ts": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, "src/lib/strings/errors.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unsafe-call": { + "count": 14 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 14 } }, - "src/locale/i18n.web.ts": { - "@typescript-eslint/no-explicit-any": { + "src/lib/translation/index.tsx": { + "react-hooks/set-state-in-effect": { "count": 1 } }, - "src/platform/polyfills.web.ts": { - "@typescript-eslint/no-explicit-any": { + "src/lib/useGetEmojis/getEmojis.ts": { + "@typescript-eslint/require-await": { + "count": 1 + } + }, + "src/logger/transports/sentry.ts": { + "@typescript-eslint/no-unsafe-enum-comparison": { + "count": 3 + } + }, + "src/logger/types.ts": { + "@typescript-eslint/no-redundant-type-constituents": { "count": 1 } }, "src/screens/Bookmarks/index.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-misused-promises": { + "count": 3 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 2 } }, "src/screens/Deactivated.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-floating-promises": { + "count": 1 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 + } + }, + "src/screens/E2E/SharedPreferencesTesterScreen.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 6 + }, + "@typescript-eslint/require-await": { + "count": 6 + } + }, + "src/screens/Feeds/NoFollowingFeed.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/screens/Feeds/NoSavedFeedsOfAnyType.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 1 + } + }, + "src/screens/Hashtag.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 2 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 + } + }, + "src/screens/Home/NoFeedsPinned.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 1 + } + }, + "src/screens/List/ListHiddenScreen.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 3 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 + } + }, + "src/screens/Log.tsx": { + "@typescript-eslint/no-unsafe-enum-comparison": { + "count": 2 } }, "src/screens/Login/ChooseAccountForm.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 } }, "src/screens/Login/ForgotPasswordForm.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-call": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 } }, "src/screens/Login/LoginForm.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-floating-promises": { + "count": 1 + }, + "@typescript-eslint/no-misused-promises": { + "count": 3 + }, + "@typescript-eslint/no-unsafe-call": { + "count": 4 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 4 + }, + "react-hooks/refs": { + "count": 1 } }, "src/screens/Login/SetNewPasswordForm.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-misused-promises": { + "count": 2 + }, + "@typescript-eslint/no-unsafe-call": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 + } + }, + "src/screens/Login/index.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 1 + }, + "react-hooks/purity": { + "count": 1 + }, + "react-hooks/refs": { + "count": 1 + }, + "react-hooks/set-state-in-effect": { + "count": 1 + } + }, + "src/screens/Messages/ChatList.tsx": { + "react-hooks/immutability": { + "count": 1 } }, "src/screens/Moderation/index.tsx": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-floating-promises": { + "count": 1 + }, + "@typescript-eslint/no-misused-promises": { + "count": 2 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 2 } }, "src/screens/ModerationInteractionSettings/index.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 + } + }, + "src/screens/Onboarding/StepFinished/ValuePropositionPager.tsx": { + "react-hooks/refs": { + "count": 4 } }, "src/screens/Onboarding/StepFinished/index.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 } }, "src/screens/Onboarding/StepInterests/index.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 + }, + "@typescript-eslint/require-await": { + "count": 1 + } + }, + "src/screens/Onboarding/StepProfile/index.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 2 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 + } + }, + "src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 1 + } + }, + "src/screens/Onboarding/StepSuggestedStarterpacks/index.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 1 } }, "src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-unsafe-call": { + "count": 2 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 4 } }, "src/screens/PostThread/index.tsx": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 3 + }, + "react-hooks/preserve-manual-memoization": { + "count": 1 + }, + "react-hooks/refs": { + "count": 4 } }, "src/screens/Profile/Header/EditProfileDialog.tsx": { "@typescript-eslint/no-explicit-any": { "count": 3 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 } }, "src/screens/Profile/Header/ProfileHeaderLabeler.tsx": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 3 } }, "src/screens/Profile/Header/Shell.tsx": { @@ -439,29 +1397,161 @@ "count": 1 } }, + "src/screens/Profile/KnownFollowers.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 2 + } + }, "src/screens/Profile/Sections/Feed.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-floating-promises": { + "count": 1 } }, "src/screens/Profile/components/GermButton.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 + } + }, + "src/screens/Profile/components/ProfileFeedHeader.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 5 + } + }, + "src/screens/ProfileList/FeedSection.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/screens/ProfileList/components/Header.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 3 + } + }, + "src/screens/ProfileList/components/MoreOptionsMenu.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + }, + "@typescript-eslint/no-misused-promises": { + "count": 5 + } + }, + "src/screens/ProfileList/components/SubscribeMenu.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 2 + } + }, + "src/screens/ProfileList/index.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/screens/SavedFeeds.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 2 + } + }, + "src/screens/Search/Shell.tsx": { + "react-hooks/refs": { + "count": 4 + } + }, + "src/screens/Search/modules/ExploreSuggestedAccounts.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/screens/Search/modules/ExploreTrendingTopics.tsx": { + "react-hooks/purity": { + "count": 2 + } + }, + "src/screens/Search/modules/ExploreTrendingVideos.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/screens/Settings/AboutSettings.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/screens/Settings/AppPasswords.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 1 + } + }, + "src/screens/Settings/AutomationLabelSettings.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 2 } }, "src/screens/Settings/FindContactsSettings.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-floating-promises": { + "count": 3 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 + }, + "@typescript-eslint/require-await": { + "count": 1 + } + }, + "src/screens/Settings/InterestsSettings.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 + }, + "@typescript-eslint/require-await": { + "count": 1 + } + }, + "src/screens/Settings/components/ChangeHandleDialog.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 4 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 } }, "src/screens/Settings/components/ChangePasswordDialog.tsx": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-misused-promises": { + "count": 2 + }, + "@typescript-eslint/no-unsafe-call": { + "count": 2 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 2 + } + }, + "src/screens/Settings/components/CopyButton.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 } }, "src/screens/Settings/components/DeactivateAccountDialog.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 } }, "src/screens/Settings/components/DeleteAccountDialog.tsx": { @@ -469,14 +1559,114 @@ "count": 2 } }, + "src/screens/Settings/components/DisableEmail2FADialog.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 4 + } + }, + "src/screens/Settings/components/OTAInfo.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/screens/Signup/StepCaptcha/CaptchaWebView.tsx": { + "react-hooks/purity": { + "count": 1 + } + }, + "src/screens/Signup/StepHandle/index.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 1 + } + }, "src/screens/Signup/StepInfo/Policies.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 } }, + "src/screens/Signup/StepInfo/index.tsx": { + "react-hooks/refs": { + "count": 3 + } + }, "src/screens/SignupQueued.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-floating-promises": { + "count": 1 + }, + "@typescript-eslint/no-misused-promises": { + "count": 2 + }, + "@typescript-eslint/no-unsafe-call": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 + }, + "react-hooks/set-state-in-effect": { + "count": 1 + } + }, + "src/screens/StarterPack/StarterPackLandingScreen.tsx": { + "@typescript-eslint/no-unsafe-call": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 + } + }, + "src/screens/StarterPack/StarterPackScreen.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + }, + "@typescript-eslint/no-misused-promises": { + "count": 2 + }, + "@typescript-eslint/require-await": { + "count": 1 + } + }, + "src/screens/StarterPack/Wizard/StepFeeds.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 1 + } + }, + "src/screens/StarterPack/Wizard/StepProfiles.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 1 + } + }, + "src/screens/StarterPack/Wizard/index.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 2 + }, + "@typescript-eslint/require-await": { + "count": 1 + } + }, + "src/screens/Topic.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 2 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 + } + }, + "src/state/a11y.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/state/cache/profile-shadow.ts": { + "react-hooks/set-state-in-effect": { + "count": 1 + } + }, + "src/state/cache/thread-mutes.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 2 } }, "src/state/cache/types.ts": { @@ -487,36 +1677,198 @@ "src/state/feed-feedback.tsx": { "@typescript-eslint/no-explicit-any": { "count": 3 + }, + "react-hooks/refs": { + "count": 2 } }, - "src/state/messages/convo/agent.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 + "src/state/gallery.ts": { + "@typescript-eslint/prefer-promise-reject-errors": { + "count": 1 } }, "src/state/messages/events/agent.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-floating-promises": { + "count": 4 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 2 + } + }, + "src/state/messages/message-drafts.tsx": { + "react-hooks/refs": { + "count": 1 } }, "src/state/persisted/index.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unsafe-call": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 } }, "src/state/persisted/index.web.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 4 } }, "src/state/persisted/util.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 + } + }, + "src/state/preferences/alt-text-required.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/state/preferences/autoplay.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/state/preferences/disable-haptics.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/state/preferences/external-embeds-prefs.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/state/preferences/hidden-posts.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/state/preferences/in-app-browser.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/state/preferences/kawaii.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + }, + "react-hooks/set-state-in-effect": { + "count": 1 + } + }, + "src/state/preferences/languages.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/state/preferences/large-alt-badge.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/state/preferences/subtitles.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/state/preferences/trending.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/state/preferences/used-starter-packs.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/state/queries/activity-subscriptions.ts": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/state/queries/actor-autocomplete.ts": { + "react-hooks/immutability": { + "count": 2 + } + }, + "src/state/queries/app-passwords.ts": { + "@typescript-eslint/no-floating-promises": { + "count": 2 + } + }, + "src/state/queries/bookmarks/useBookmarkMutation.ts": { + "@typescript-eslint/no-floating-promises": { + "count": 2 + } + }, + "src/state/queries/bookmarks/useBookmarksQuery.ts": { + "@typescript-eslint/require-await": { + "count": 2 + } + }, + "src/state/queries/explore-feed-previews.tsx": { + "react-hooks/refs": { + "count": 4 + } + }, + "src/state/queries/feed.ts": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/state/queries/handle.ts": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/state/queries/list.ts": { + "@typescript-eslint/no-floating-promises": { + "count": 7 + } + }, + "src/state/queries/my-lists.ts": { + "@typescript-eslint/no-floating-promises": { + "count": 2 } }, "src/state/queries/notifications/feed.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-floating-promises": { + "count": 2 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 2 + } + }, + "src/state/queries/notifications/unread.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 4 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 4 + }, + "react-hooks/refs": { + "count": 1 } }, "src/state/queries/nuxs/definitions.ts": { @@ -527,31 +1879,93 @@ "src/state/queries/pinned-post.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-floating-promises": { + "count": 2 } }, "src/state/queries/post-feed.ts": { "@typescript-eslint/no-explicit-any": { "count": 3 + }, + "@typescript-eslint/no-floating-promises": { + "count": 3 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 3 } }, "src/state/queries/postgate/index.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-floating-promises": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-call": { + "count": 2 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 3 + }, + "@typescript-eslint/require-await": { + "count": 2 + } + }, + "src/state/queries/preferences/index.ts": { + "react-hooks/immutability": { + "count": 1 + } + }, + "src/state/queries/preferences/useThreadPreferences.ts": { + "react-hooks/refs": { + "count": 2 } }, "src/state/queries/search-posts.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 2 + } + }, + "src/state/queries/starter-packs.ts": { + "@typescript-eslint/require-await": { + "count": 1 + }, + "react-hooks/immutability": { + "count": 1 + } + }, + "src/state/queries/suggested-follows.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "src/state/queries/threadgate/index.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-floating-promises": { + "count": 3 + }, + "@typescript-eslint/no-unsafe-call": { + "count": 2 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 2 + }, + "@typescript-eslint/require-await": { + "count": 3 } }, "src/state/queries/util.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 } }, "src/state/session/agent.ts": { @@ -559,84 +1973,269 @@ "count": 1 } }, + "src/state/shell/color-mode.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 2 + } + }, + "src/state/shell/composer/index.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/state/shell/onboarding.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 5 + } + }, "src/state/shell/progress-guide.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-floating-promises": { + "count": 4 + } + }, + "src/state/shell/reminders.ts": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/state/shell/tick-every-minute.tsx": { + "react-hooks/purity": { + "count": 1 + } + }, + "src/storage/archive/index.ts": { + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 } }, "src/storage/index.ts": { "@typescript-eslint/no-explicit-any": { "count": 8 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 + } + }, + "src/view/com/auth/SplashScreen.web.tsx": { + "react-hooks/set-state-in-effect": { + "count": 1 } }, "src/view/com/composer/Composer.tsx": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-floating-promises": { + "count": 1 + }, + "@typescript-eslint/no-misused-promises": { + "count": 4 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 2 + }, + "react-hooks/immutability": { + "count": 2 + }, + "react-hooks/preserve-manual-memoization": { + "count": 3 + }, + "react-hooks/set-state-in-effect": { + "count": 1 + } + }, + "src/view/com/composer/SelectMediaButton.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 + } + }, + "src/view/com/composer/drafts/DraftsButton.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 1 } }, "src/view/com/composer/drafts/state/queries.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-floating-promises": { + "count": 2 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 2 + } + }, + "src/view/com/composer/drafts/state/storage.ts": { + "@typescript-eslint/require-await": { + "count": 3 + } + }, + "src/view/com/composer/photos/EditImageDialog.web.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 1 + } + }, + "src/view/com/composer/photos/Gallery.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 } }, "src/view/com/composer/photos/OpenCameraBtn.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 + } + }, + "src/view/com/composer/select-language/SuggestedLanguage.tsx": { + "react-hooks/refs": { + "count": 1 + }, + "react-hooks/set-state-in-effect": { + "count": 1 + } + }, + "src/view/com/composer/text-input/TextInput.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + }, + "@typescript-eslint/no-misused-promises": { + "count": 1 + } + }, + "src/view/com/composer/text-input/TextInput.web.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 1 + }, + "react-hooks/refs": { + "count": 1 + } + }, + "src/view/com/composer/text-input/web/Autocomplete.tsx": { + "@typescript-eslint/no-unsafe-member-access": { + "count": 2 + }, + "react-hooks/set-state-in-effect": { + "count": 1 + } + }, + "src/view/com/composer/videos/pickVideo.web.ts": { + "@typescript-eslint/no-misused-promises": { + "count": 1 } }, "src/view/com/feeds/ComposerPrompt.tsx": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-floating-promises": { + "count": 2 + } + }, + "src/view/com/feeds/FeedPage.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 2 } }, "src/view/com/feeds/ProfileFeedgens.tsx": { "@typescript-eslint/no-explicit-any": { "count": 3 - } - }, - "src/view/com/lightbox/ImageViewing/@types/index.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "src/view/com/lightbox/ImageViewing/index.tsx": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "src/view/com/lists/ListMembers.tsx": { - "@typescript-eslint/no-explicit-any": { + }, + "@typescript-eslint/no-floating-promises": { + "count": 2 + }, + "@typescript-eslint/no-misused-promises": { "count": 3 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 2 } }, "src/view/com/lists/MyLists.tsx": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-misused-promises": { + "count": 3 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 6 } }, "src/view/com/lists/ProfileLists.tsx": { "@typescript-eslint/no-explicit-any": { "count": 3 + }, + "@typescript-eslint/no-floating-promises": { + "count": 2 + }, + "@typescript-eslint/no-misused-promises": { + "count": 3 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 2 + } + }, + "src/view/com/modals/Modal.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 1 + }, + "@typescript-eslint/require-await": { + "count": 1 } }, "src/view/com/notifications/NotificationFeed.tsx": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-floating-promises": { + "count": 1 + }, + "@typescript-eslint/no-misused-promises": { + "count": 2 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 6 + }, + "react-hooks/set-state-in-effect": { + "count": 1 } }, "src/view/com/notifications/NotificationFeedItem.tsx": { "@typescript-eslint/no-explicit-any": { "count": 3 + }, + "@typescript-eslint/no-misused-promises": { + "count": 3 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 2 } }, "src/view/com/pager/Pager.tsx": { "@typescript-eslint/no-explicit-any": { "count": 4 + }, + "react-hooks/refs": { + "count": 1 + } + }, + "src/view/com/pager/Pager.web.tsx": { + "react-hooks/immutability": { + "count": 1 + }, + "react-hooks/preserve-manual-memoization": { + "count": 1 + }, + "react-hooks/refs": { + "count": 1 } }, "src/view/com/pager/PagerWithHeader.tsx": { @@ -649,14 +2248,46 @@ "count": 2 } }, + "src/view/com/pager/TabBar.tsx": { + "react-hooks/immutability": { + "count": 1 + } + }, "src/view/com/pager/TabBar.web.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unsafe-call": { + "count": 2 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 4 + } + }, + "src/view/com/post-thread/PostLikedBy.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 2 + } + }, + "src/view/com/post-thread/PostQuotes.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 2 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 + } + }, + "src/view/com/post-thread/PostRepostedBy.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 2 } }, "src/view/com/posts/FeedShutdownMsg.tsx": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-misused-promises": { + "count": 2 } }, "src/view/com/posts/PostFeed.tsx": { @@ -667,16 +2298,30 @@ "src/view/com/posts/PostFeedErrorMessage.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unsafe-call": { + "count": 8 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 9 } }, - "src/view/com/profile/ProfileMenu.tsx": { - "@typescript-eslint/no-explicit-any": { - "count": 6 + "src/view/com/profile/ProfileFollowers.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 2 + } + }, + "src/view/com/profile/ProfileFollows.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 2 } }, "src/view/com/testing/TestCtrls.e2e.tsx": { "@typescript-eslint/no-explicit-any": { - "count": 2 + "count": 1 + }, + "@typescript-eslint/no-misused-promises": { + "count": 9 } }, "src/view/com/util/EmptyState.tsx": { @@ -687,11 +2332,23 @@ "src/view/com/util/ErrorBoundary.tsx": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-unsafe-call": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 } }, "src/view/com/util/EventStopper.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unsafe-call": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 } }, "src/view/com/util/Link.tsx": { @@ -699,14 +2356,9 @@ "count": 2 } }, - "src/view/com/util/List.tsx": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "src/view/com/util/List.web.tsx": { - "@typescript-eslint/no-explicit-any": { - "count": 14 + "src/view/com/util/LoadingPlaceholder.tsx": { + "react-hooks/purity": { + "count": 1 } }, "src/view/com/util/MainScrollProvider.tsx": { @@ -714,9 +2366,22 @@ "count": 2 } }, + "src/view/com/util/UserAvatar.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 3 + } + }, + "src/view/com/util/UserBanner.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 3 + } + }, "src/view/com/util/ViewSelector.tsx": { "@typescript-eslint/no-explicit-any": { "count": 6 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 } }, "src/view/com/util/Views.tsx": { @@ -724,24 +2389,96 @@ "count": 1 } }, - "src/view/com/util/fab/FABInner.tsx": { - "@typescript-eslint/no-explicit-any": { + "src/view/com/util/Views.web.tsx": { + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 + } + }, + "src/view/com/util/forms/Button.tsx": { + "@typescript-eslint/no-misused-promises": { "count": 1 } }, "src/view/screens/Debug.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 4 } }, - "src/view/screens/DebugMod.tsx": { - "@typescript-eslint/no-explicit-any": { + "src/view/screens/Feeds.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 3 + }, + "@typescript-eslint/no-misused-promises": { "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 + } + }, + "src/view/screens/Home.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 1 + } + }, + "src/view/screens/ModerationBlockedAccounts.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 3 + } + }, + "src/view/screens/ModerationMutedAccounts.tsx": { + "@typescript-eslint/no-misused-promises": { + "count": 3 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 1 + } + }, + "src/view/screens/Notifications.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 3 + } + }, + "src/view/screens/Storybook/ListContained.tsx": { + "@typescript-eslint/no-unsafe-call": { + "count": 1 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 2 + } + }, + "src/view/shell/Drawer.tsx": { + "@typescript-eslint/no-floating-promises": { + "count": 2 } }, "src/view/shell/createNativeStackNavigatorWithAuth.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unsafe-call": { + "count": 1 + }, + "react-hooks/refs": { + "count": 13 + } + }, + "src/view/shell/desktop/Search.tsx": { + "@typescript-eslint/no-unsafe-call": { + "count": 3 + }, + "@typescript-eslint/no-unsafe-member-access": { + "count": 3 + }, + "react-hooks/refs": { + "count": 3 + } + }, + "src/view/shell/index.web.tsx": { + "react-hooks/set-state-in-effect": { + "count": 1 } } } \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs index 6293ddfd02..c8addf66ae 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,7 +1,7 @@ // @ts-check import js from '@eslint/js' import tseslint from 'typescript-eslint' -import { defineConfig } from 'eslint/config'; +import {defineConfig} from 'eslint/config' import react from 'eslint-plugin-react' import reactHooks from 'eslint-plugin-react-hooks' // @ts-expect-error no types @@ -32,6 +32,7 @@ export default defineConfig( '*.html', 'bskyweb/**', 'bskyembed/**', + 'bskyogcard/**', 'src/locale/locales/_build/**', 'src/locale/locales/**/*.js', '*.e2e.ts', @@ -133,11 +134,10 @@ export default defineConfig( 'react-native/no-inline-styles': 'off', ...reactNativeA11y.configs.all.rules, 'react-compiler/react-compiler': 'warn', - // TODO: Fix these and set to error - 'react-hooks/set-state-in-effect': 'warn', - 'react-hooks/purity': 'warn', - 'react-hooks/refs': 'warn', - 'react-hooks/immutability': 'warn', + 'react-hooks/set-state-in-effect': 'error', + 'react-hooks/purity': 'error', + 'react-hooks/refs': 'error', + 'react-hooks/immutability': 'error', /** * Import sorting @@ -183,24 +183,30 @@ export default defineConfig( * Import linting */ 'import-x/consistent-type-specifier-style': ['warn', 'prefer-inline'], - 'import-x/no-unresolved': ['error', { - /* - * The `postinstall` hook runs `compile-if-needed` locally, but not in - * CI. For CI-sake, ignore this. - */ - ignore: ['^#\/locale\/locales\/.+\/messages'], - }], - 'import-x/no-extraneous-dependencies': ['error', { - 'whitelist': [ - // test files only - '@jest/globals', - // we only use a really simple util from this, and we know it will be present - 'expo-modules-core', - // this is a dep for @atproto/api, but we absolutely need them in sync, so just - // rely on the transient version - '@atproto/common-web', - ] - }], + 'import-x/no-unresolved': [ + 'error', + { + /* + * The `postinstall` hook runs `compile-if-needed` locally, but not in + * CI. For CI-sake, ignore this. + */ + ignore: ['^#\/locale\/locales\/.+\/messages'], + }, + ], + 'import-x/no-extraneous-dependencies': [ + 'error', + { + whitelist: [ + // test files only + '@jest/globals', + // we only use a really simple util from this, and we know it will be present + 'expo-modules-core', + // this is a dep for @atproto/api, but we absolutely need them in sync, so just + // rely on the transient version + '@atproto/common-web', + ], + }, + ], 'import-x/no-nodejs-modules': 'error', /** @@ -221,13 +227,15 @@ export default defineConfig( {prefer: 'type-imports', fixStyle: 'inline-type-imports'}, ], '@typescript-eslint/no-require-imports': 'off', - '@typescript-eslint/no-unused-expressions': ['error', { - allowTernary: true, - }], + '@typescript-eslint/no-unused-expressions': [ + 'error', + { + allowTernary: true, + }, + ], /** - * Maintain previous behavior - these are stricter in typescript-eslint - * v8 `warn` ones are probably worth fixing. `off` ones are a bit too - * nit-picky + * Maintain previous behavior via eslint-suppressions.json - these are + * stricter in typescript-eslint v8. `off` ones are a bit too nit-picky. */ '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/ban-ts-comment': 'off', @@ -237,26 +245,32 @@ export default defineConfig( '@typescript-eslint/unbound-method': 'off', '@typescript-eslint/no-unsafe-argument': 'off', '@typescript-eslint/no-unsafe-return': 'off', - '@typescript-eslint/no-unsafe-member-access': 'warn', - '@typescript-eslint/no-unsafe-call': 'warn', - '@typescript-eslint/no-floating-promises': 'warn', - '@typescript-eslint/no-misused-promises': 'warn', - '@typescript-eslint/require-await': 'warn', - '@typescript-eslint/no-unsafe-enum-comparison': 'warn', - '@typescript-eslint/no-unnecessary-type-assertion': 'warn', - '@typescript-eslint/no-redundant-type-constituents': 'warn', - '@typescript-eslint/no-duplicate-type-constituents': 'warn', - '@typescript-eslint/no-base-to-string': 'warn', - '@typescript-eslint/prefer-promise-reject-errors': 'warn', - '@typescript-eslint/await-thenable': 'warn', + '@typescript-eslint/no-unsafe-member-access': 'error', + '@typescript-eslint/no-unsafe-call': 'error', + '@typescript-eslint/no-floating-promises': 'error', + '@typescript-eslint/no-misused-promises': 'error', + '@typescript-eslint/require-await': 'error', + '@typescript-eslint/no-unsafe-enum-comparison': 'error', + '@typescript-eslint/no-unnecessary-type-assertion': 'error', + '@typescript-eslint/no-redundant-type-constituents': 'error', + '@typescript-eslint/no-duplicate-type-constituents': 'error', + '@typescript-eslint/no-base-to-string': 'error', + '@typescript-eslint/prefer-promise-reject-errors': 'error', + '@typescript-eslint/await-thenable': 'error', - "no-restricted-imports": ["error", { - "paths": [{ - "name": "react", - "importNames": ["React", "default"], - "message": "React is already in the global type namespace. Use named imports for runtime modules." - }] - }], + 'no-restricted-imports': [ + 'error', + { + paths: [ + { + name: 'react', + importNames: ['React', 'default'], + message: + 'React is already in the global type namespace. Use named imports for runtime modules.', + }, + ], + }, + ], /** * Turn off rules that we haven't enforced thus far @@ -279,6 +293,16 @@ export default defineConfig( }, }, + /** + * bskyogcard, dev-env - server-side, Node.js imports are fine + */ + { + files: ['bskyogcard/**/*.{js,jsx,ts,tsx}', 'dev-env/**/*.{js,jsx,ts,tsx}'], + rules: { + 'import-x/no-nodejs-modules': 'off', + }, + }, + /** * Test files configuration */ @@ -287,7 +311,7 @@ export default defineConfig( languageOptions: { globals: { ...globals.jest, - } + }, }, }, ) diff --git a/jest/jestSetup.js b/jest/jestSetup.js index 73a509d036..ca04d54261 100644 --- a/jest/jestSetup.js +++ b/jest/jestSetup.js @@ -17,10 +17,6 @@ jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter', () => { } }) -jest.mock('@fortawesome/react-native-fontawesome', () => ({ - FontAwesomeIcon: '', -})) - jest.mock('react-native-safe-area-context', () => { const inset = {top: 0, right: 0, bottom: 0, left: 0} return { diff --git a/metro.config.js b/metro.config.js index dcdead3f5d..363956a77d 100644 --- a/metro.config.js +++ b/metro.config.js @@ -2,6 +2,7 @@ const {getSentryExpoConfig} = require('@sentry/react-native/metro') const cfg = getSentryExpoConfig(__dirname) +// inject `.e2e.ts` and `.e2e.tsx` into the sourceExts when running tests cfg.resolver.sourceExts = process.env.RN_SRC_EXT ? process.env.RN_SRC_EXT.split(',').concat(cfg.resolver.sourceExts) : cfg.resolver.sourceExts @@ -16,25 +17,7 @@ if (process.env.BSKY_PROFILE) { cfg.resolver.assetExts = [...cfg.resolver.assetExts, 'woff2'] -// Enabled by default in RN 0.79+, but this breaks Lingui + others -cfg.resolver.unstable_enablePackageExports = false - cfg.resolver.resolveRequest = (context, moduleName, platform) => { - // HACK: manually resolve a few packages that use `exports` in `package.json`. - // A proper solution is to enable `unstable_enablePackageExports` but this needs careful testing. - if (moduleName.startsWith('multiformats/hashes/hasher')) { - return context.resolveRequest( - context, - 'multiformats/cjs/src/hashes/hasher', - platform, - ) - } - if (moduleName.startsWith('multiformats/cid')) { - return context.resolveRequest(context, 'multiformats/cjs/src/cid', platform) - } - if (moduleName === '@ipld/dag-cbor') { - return context.resolveRequest(context, '@ipld/dag-cbor/src', platform) - } if (process.env.BSKY_PROFILE) { if (moduleName.endsWith('ReactNativeRenderer-prod')) { return context.resolveRequest( @@ -51,16 +34,6 @@ cfg.transformer.getTransformOptions = async () => ({ transform: { experimentalImportSupport: true, inlineRequires: true, - nonInlinedRequires: [ - // We can remove this option and rely on the default after - // https://github.com/facebook/metro/pull/1390 is released. - 'React', - 'react', - 'react-compiler-runtime', - 'react/jsx-dev-runtime', - 'react/jsx-runtime', - 'react-native', - ], }, }) diff --git a/modules/BlueskyClip/README.md b/modules/BlueskyClip/README.md index 147ee50e6d..dfe5106ddf 100644 --- a/modules/BlueskyClip/README.md +++ b/modules/BlueskyClip/README.md @@ -126,9 +126,9 @@ modules/BlueskyClip/ ## Development Notes -- The App Clip is built as part of the main Xcode project when running `yarn prebuild` +- The App Clip is built as part of the main Xcode project when running `pnpm prebuild` - Source files are copied during the prebuild process, not directly referenced -- Changes to Swift files require running `yarn prebuild` to take effect +- Changes to Swift files require running `pnpm prebuild` to take effect - The App Clip shares the same version number as the main app - App Clips have a 15MB size limit (enforced by Apple) - Users can convert an App Clip session into a full app install without losing data (via shared App Group) diff --git a/modules/BlueskyNSE/NotificationService.swift b/modules/BlueskyNSE/NotificationService.swift index b441f48a91..db3d261b56 100644 --- a/modules/BlueskyNSE/NotificationService.swift +++ b/modules/BlueskyNSE/NotificationService.swift @@ -1,6 +1,6 @@ -import UserNotifications -import UIKit import Intents +import UIKit +import UserNotifications let APP_GROUP = "group.app.bsky" typealias ContentHandler = (UNNotificationContent) -> Void @@ -30,11 +30,14 @@ class NotificationService: UNNotificationServiceExtension { private var contentHandler: ContentHandler? private var bestAttempt: UNMutableNotificationContent? - override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { + override func didReceive( + _ request: UNNotificationRequest, + withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void + ) { self.contentHandler = contentHandler guard let bestAttempt = NSEUtil.createCopy(request.content), - let reason = request.content.userInfo["reason"] as? String + let reason = request.content.userInfo["reason"] as? String else { contentHandler(request.content) return @@ -44,11 +47,26 @@ class NotificationService: UNNotificationServiceExtension { if reason == "chat-message" || reason == "chat-reaction" { mutateWithChatMessage(bestAttempt) + // Only apply the title->body swap for chat-message. For chat-reaction, + // `messageKind` refers to the reacted-to message, so we'd clobber the + // descriptive reaction body with the title for reactions on system + // messages. + if reason == "chat-message" { + mutateChatMessageBody(bestAttempt, userInfo: request.content.userInfo) + } + mutateWithGroupSubtitle(bestAttempt, userInfo: request.content.userInfo) let finalContent = createCommunicationNotification( from: bestAttempt, userInfo: request.content.userInfo ) contentHandler(finalContent) + } else if reason == "chat-added-to-group" + || reason == "chat-removed-from-group" + || reason == "chat-join-request-rejected" { + mutateWithChatMessage(bestAttempt) + mutateWithGroupSubtitle(bestAttempt, userInfo: request.content.userInfo) + mutateWithBadge(bestAttempt) + contentHandler(bestAttempt) } else { mutateWithBadge(bestAttempt) contentHandler(bestAttempt) @@ -57,7 +75,8 @@ class NotificationService: UNNotificationServiceExtension { override func serviceExtensionTimeWillExpire() { guard let contentHandler = self.contentHandler, - let bestAttempt = self.bestAttempt else { + let bestAttempt = self.bestAttempt + else { return } contentHandler(bestAttempt) @@ -71,7 +90,7 @@ class NotificationService: UNNotificationServiceExtension { ) -> UNNotificationContent { let senderDisplayName = userInfo["senderDisplayName"] as? String ?? "Unknown" let convoId = userInfo["convoId"] as? String - var avatarImage: INImage? = nil + var avatarImage: INImage? if let avatarUrlString = userInfo["senderAvatarUrl"] as? String { avatarImage = downloadAvatarImage(from: avatarUrlString) } @@ -87,17 +106,53 @@ class NotificationService: UNNotificationServiceExtension { customIdentifier: nil ) + var speakableGroupName: INSpeakableString? + if userInfo["convoKind"] as? String == "group", + let groupName = userInfo["groupName"] as? String, + !groupName.isEmpty { + speakableGroupName = INSpeakableString(spokenPhrase: groupName) + } + let intent = INSendMessageIntent( recipients: nil, outgoingMessageType: .outgoingMessageText, content: content.body, - speakableGroupName: nil, + speakableGroupName: speakableGroupName, conversationIdentifier: convoId, serviceName: nil, sender: sender, attachments: nil ) + // The push payload omits the full recipient list for group convos. Without + // any signal of multiple recipients, iOS classifies the notification as + // `MessagingDirect` and ignores `speakableGroupName`. Setting + // `recipientCount` on the donation metadata is Apple's documented escape + // hatch for this case, and overrides the (zero-length) recipients array. + if userInfo["convoKind"] as? String == "group", + let groupMemberCount = userInfo["groupMemberCount"] as? Int, + groupMemberCount > 0 { + let metadata = INSendMessageIntentDonationMetadata() + metadata.recipientCount = groupMemberCount + intent.donationMetadata = metadata + } + + // For group convos, attach the composite group avatar (rendered by the + // ogcard service) to the `speakableGroupName` parameter so iOS shows it + // alongside the sender on the Communication Notification. + // Disabled: the composite avatar renders poorly at notification size. + // if userInfo["convoKind"] as? String == "group", + // let convoAvatarUrlString = userInfo["convoAvatarUrl"] as? String, + // let groupImage = downloadAvatarImage(from: convoAvatarUrlString) { + // intent.setImage(groupImage, forParameterNamed: \.speakableGroupName) + // } + + // Set the group image to the sender avatar instead + if userInfo["convoKind"] as? String == "group", + let avatarImage { + intent.setImage(avatarImage, forParameterNamed: \.speakableGroupName) + } + let interaction = INInteraction(intent: intent, response: nil) interaction.direction = .incoming interaction.donate(completion: nil) @@ -120,13 +175,13 @@ class NotificationService: UNNotificationServiceExtension { var request = URLRequest(url: url) request.timeoutInterval = 5 - var imageData: Data? = nil + var imageData: Data? let semaphore = DispatchSemaphore(value: 0) - let task = URLSession.shared.dataTask(with: request) { data, response, error in + let task = URLSession.shared.dataTask(with: request) { data, response, _ in if let data = data, - let httpResponse = response as? HTTPURLResponse, - httpResponse.statusCode == 200 { + let httpResponse = response as? HTTPURLResponse, + httpResponse.statusCode == 200 { imageData = data } semaphore.signal() @@ -157,6 +212,40 @@ class NotificationService: UNNotificationServiceExtension { } } + // For group convos, surface the group name as the notification subtitle. + // The sender's display name is shown as the title (overridden by + // `INSendMessageIntent` for chat-message/chat-reaction). + func mutateWithGroupSubtitle( + _ content: UNMutableNotificationContent, + userInfo: [AnyHashable: Any] + ) { + guard userInfo["convoKind"] as? String == "group", + let groupName = userInfo["groupName"] as? String, + !groupName.isEmpty + else { + return + } + content.subtitle = groupName + } + + // System messages (`add_member`, `convo_locked`, `edit_group`, etc.) are + // delivered through `chat-message` but have a server-rendered description + // in `title`. iOS overrides the title with the sender name once we apply + // `INSendMessageIntent`, so we move the title text into the body before + // building the intent. + func mutateChatMessageBody( + _ content: UNMutableNotificationContent, + userInfo: [AnyHashable: Any] + ) { + guard let messageKind = userInfo["messageKind"] as? String, + messageKind != "message", + !content.title.isEmpty + else { + return + } + content.body = content.title + } + func mutateWithDefaultSound(_ content: UNMutableNotificationContent) { content.sound = UNNotificationSound.default } diff --git a/modules/bottom-sheet/android/src/main/java/expo/modules/bottomsheet/BottomSheetView.kt b/modules/bottom-sheet/android/src/main/java/expo/modules/bottomsheet/BottomSheetView.kt index b7e81a61f6..1b9224c6e7 100644 --- a/modules/bottom-sheet/android/src/main/java/expo/modules/bottomsheet/BottomSheetView.kt +++ b/modules/bottom-sheet/android/src/main/java/expo/modules/bottomsheet/BottomSheetView.kt @@ -41,11 +41,14 @@ class BottomSheetView( private val screenHeight: Float = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) { // API 35+: edge-to-edge is mandatory, heightPixels is the full display - context.resources.displayMetrics.heightPixels.toFloat() + context.resources.displayMetrics.heightPixels + .toFloat() } else if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) { // API 30-34: heightPixels may exclude nav bar, use currentWindowMetrics val wm = context.getSystemService(Context.WINDOW_SERVICE) as android.view.WindowManager - wm.currentWindowMetrics.bounds.height().toFloat() + wm.currentWindowMetrics.bounds + .height() + .toFloat() } else { // API < 30: currentWindowMetrics not available, use getRealSize // which includes system bars (heightPixels may exclude them) @@ -166,6 +169,7 @@ class BottomSheetView( when { // Full height sheets contentHeight >= screenHeight -> 0.99f + else -> this.clampRatio(this.getTargetHeight() / screenHeight) } @@ -271,8 +275,9 @@ class BottomSheetView( } // Apply deferred layout update after gesture completes if (newState != BottomSheetBehavior.STATE_DRAGGING && - newState != BottomSheetBehavior.STATE_SETTLING && - pendingLayoutUpdate) { + newState != BottomSheetBehavior.STATE_SETTLING && + pendingLayoutUpdate + ) { pendingLayoutUpdate = false updateLayout() } @@ -292,7 +297,6 @@ class BottomSheetView( if (!fullHeight) { this.startObservingContentHeight() } - } fun updateLayout() { @@ -365,17 +369,18 @@ class BottomSheetView( val innerViewGroup = this.innerView as? ViewGroup ?: return - val listener = OnLayoutChangeListener { _, _, top, _, bottom, _, _, oldTop, oldBottom -> - val newHeight = bottom - top - val oldHeight = oldBottom - oldTop - if (newHeight != oldHeight) { - val contentHeight = getContentHeight() - if (contentHeight != lastObservedContentHeight && contentHeight > 0 && (isOpen || isOpening) && !isClosing) { - lastObservedContentHeight = contentHeight - updateLayout() + val listener = + OnLayoutChangeListener { _, _, top, _, bottom, _, _, oldTop, oldBottom -> + val newHeight = bottom - top + val oldHeight = oldBottom - oldTop + if (newHeight != oldHeight) { + val contentHeight = getContentHeight() + if (contentHeight != lastObservedContentHeight && contentHeight > 0 && (isOpen || isOpening) && !isClosing) { + lastObservedContentHeight = contentHeight + updateLayout() + } } } - } val children = mutableListOf() for (i in 0 until innerViewGroup.childCount) { diff --git a/modules/bottom-sheet/src/BottomSheet.web.tsx b/modules/bottom-sheet/src/BottomSheet.web.tsx index 1813d54dda..a7e2d46393 100644 --- a/modules/bottom-sheet/src/BottomSheet.web.tsx +++ b/modules/bottom-sheet/src/BottomSheet.web.tsx @@ -1,4 +1,4 @@ -import {BottomSheetViewProps} from './BottomSheet.types' +import {type BottomSheetViewProps} from './BottomSheet.types' export function BottomSheet(_: BottomSheetViewProps) { throw new Error('BottomSheet is not available on web') diff --git a/modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider.tsx b/modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider.tsx index 6ecdd1d476..fed40c38bf 100644 --- a/modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider.tsx +++ b/modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider.tsx @@ -1,6 +1,6 @@ import React from 'react' -import {BackgroundNotificationHandlerPreferences} from './ExpoBackgroundNotificationHandler.types' +import {type BackgroundNotificationHandlerPreferences} from './ExpoBackgroundNotificationHandler.types' import {BackgroundNotificationHandler} from './ExpoBackgroundNotificationHandlerModule' interface BackgroundNotificationPreferencesContext { diff --git a/modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandlerModule.ts b/modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandlerModule.ts index d6517893ad..1c18755a26 100644 --- a/modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandlerModule.ts +++ b/modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandlerModule.ts @@ -1,6 +1,6 @@ import {requireNativeModule} from 'expo-modules-core' -import {ExpoBackgroundNotificationHandlerModule} from './ExpoBackgroundNotificationHandler.types' +import {type ExpoBackgroundNotificationHandlerModule} from './ExpoBackgroundNotificationHandler.types' export const BackgroundNotificationHandler = requireNativeModule( diff --git a/modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandlerModule.web.ts b/modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandlerModule.web.ts index 893548e183..d417e181c1 100644 --- a/modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandlerModule.web.ts +++ b/modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandlerModule.web.ts @@ -1,6 +1,6 @@ import { - BackgroundNotificationHandlerPreferences, - ExpoBackgroundNotificationHandlerModule, + type BackgroundNotificationHandlerPreferences, + type ExpoBackgroundNotificationHandlerModule, } from './ExpoBackgroundNotificationHandler.types' // Stub for web diff --git a/modules/expo-bluesky-gif-view/src/GifView.tsx b/modules/expo-bluesky-gif-view/src/GifView.tsx index 87258de17b..0cca26cf5e 100644 --- a/modules/expo-bluesky-gif-view/src/GifView.tsx +++ b/modules/expo-bluesky-gif-view/src/GifView.tsx @@ -2,7 +2,7 @@ import React from 'react' import {requireNativeModule} from 'expo' import {requireNativeViewManager} from 'expo-modules-core' -import {GifViewProps} from './GifView.types' +import {type GifViewProps} from './GifView.types' const NativeModule = requireNativeModule('ExpoBlueskyGifView') const NativeView: React.ComponentType< diff --git a/modules/expo-bluesky-gif-view/src/GifView.types.ts b/modules/expo-bluesky-gif-view/src/GifView.types.ts index 29ec277f2b..d3b825e4d1 100644 --- a/modules/expo-bluesky-gif-view/src/GifView.types.ts +++ b/modules/expo-bluesky-gif-view/src/GifView.types.ts @@ -1,4 +1,4 @@ -import {ViewProps} from 'react-native' +import {type ViewProps} from 'react-native' export interface GifViewStateChangeEvent { nativeEvent: { @@ -10,6 +10,12 @@ export interface GifViewStateChangeEvent { export interface GifViewProps extends ViewProps { autoplay?: boolean source?: string + /** + * Web-only ordered list of `` tags rendered inside ` @@ -266,9 +268,11 @@ export const InnerFlatList = forwardRef< export function FlatListFooter({ children, onLayout, + border = true, }: { children: React.ReactNode onLayout?: (event: LayoutChangeEvent) => void + border?: boolean }) { const t = useTheme() @@ -281,7 +285,7 @@ export function FlatListFooter({ a.w_full, a.z_10, t.atoms.bg, - a.border_t, + border && a.border_t, t.atoms.border_contrast_low, a.px_lg, a.py_md, diff --git a/src/components/Dialog/types.ts b/src/components/Dialog/types.ts index 865083d501..79c40942de 100644 --- a/src/components/Dialog/types.ts +++ b/src/components/Dialog/types.ts @@ -79,14 +79,18 @@ export type DialogInnerProps = accessibilityLabelledBy: A11yProps['aria-labelledby'] accessibilityDescribedBy: string keyboardDismissMode?: ScrollViewProps['keyboardDismissMode'] + showsVerticalScrollIndicator?: ScrollViewProps['showsVerticalScrollIndicator'] contentContainerStyle?: StyleProp header?: React.ReactNode + footer?: React.ReactNode }> | DialogInnerPropsBase<{ label: string accessibilityLabelledBy?: undefined accessibilityDescribedBy?: undefined keyboardDismissMode?: ScrollViewProps['keyboardDismissMode'] + showsVerticalScrollIndicator?: ScrollViewProps['showsVerticalScrollIndicator'] contentContainerStyle?: StyleProp header?: React.ReactNode + footer?: React.ReactNode }> diff --git a/src/components/Error.tsx b/src/components/Error.tsx index 77aacdb451..190165a2df 100644 --- a/src/components/Error.tsx +++ b/src/components/Error.tsx @@ -1,12 +1,10 @@ import {View} from 'react-native' -import {msg} from '@lingui/core/macro' -import {useLingui} from '@lingui/react' -import {Trans} from '@lingui/react/macro' +import {Trans, useLingui} from '@lingui/react/macro' import {useGoBack} from '#/lib/hooks/useGoBack' -import {CenteredView} from '#/view/com/util/Views' import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {Button, ButtonText} from '#/components/Button' +import * as Layout from '#/components/Layout' import {Text} from '#/components/Typography' export function Error({ @@ -15,22 +13,20 @@ export function Error({ onRetry, onGoBack, hideBackButton, - sideBorders = true, }: { title?: string message?: string onRetry?: () => unknown onGoBack?: () => unknown hideBackButton?: boolean - sideBorders?: boolean }) { - const {_} = useLingui() + const {t: l} = useLingui() const t = useTheme() const {gtMobile} = useBreakpoints() const goBack = useGoBack(onGoBack) return ( - + ]}> {title} @@ -70,7 +65,7 @@ export function Error({ )} - + ) } diff --git a/src/components/KnownFollowers.tsx b/src/components/KnownFollowers.tsx index b26216b2f7..502b08252f 100644 --- a/src/components/KnownFollowers.tsx +++ b/src/components/KnownFollowers.tsx @@ -5,9 +5,7 @@ import { moderateProfile, type ModerationOpts, } from '@atproto/api' -import {msg} from '@lingui/core/macro' -import {useLingui} from '@lingui/react' -import {Plural, Trans} from '@lingui/react/macro' +import {Plural, Trans, useLingui} from '@lingui/react/macro' import {makeProfileLink} from '#/lib/routes/links' import {sanitizeDisplayName} from '#/lib/strings/display-names' @@ -93,7 +91,7 @@ function KnownFollowersInner({ showIfEmpty?: boolean }) { const t = useTheme() - const {_} = useLingui() + const {t: l} = useLingui() const textStyle = [a.text_sm, a.leading_snug, t.atoms.text_contrast_medium] @@ -124,9 +122,7 @@ function KnownFollowersInner({ return ( {slice.length >= 2 ? ( // 2-n followers, including blocks - serverCount > 2 ? ( // only 2 + // only 2 + serverCount > 2 ? ( Followed by{' '} @@ -254,7 +251,7 @@ function EmptyFallback({show}: {show?: boolean}) { return ( - Not followed by anyone you're following + Not followed by anyone you’re following ) } diff --git a/src/components/Layout/Header/index.tsx b/src/components/Layout/Header/index.tsx index 8109914dce..f2f5ba122f 100644 --- a/src/components/Layout/Header/index.tsx +++ b/src/components/Layout/Header/index.tsx @@ -7,6 +7,7 @@ import {useNavigation} from '@react-navigation/native' import {HITSLOP_30} from '#/lib/constants' import {type NavigationProp} from '#/lib/routes/types' import {useSetDrawerOpen} from '#/state/shell' +import {useIsWithinSplitView} from '#/screens/Messages/components/splitView/context' import { atoms as a, platform, @@ -23,11 +24,13 @@ import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu' import { BUTTON_VISUAL_ALIGNMENT_OFFSET, CENTER_COLUMN_OFFSET, + CENTER_COLUMN_WIDTH, HEADER_SLOT_SIZE, SCROLLBAR_OFFSET, } from '#/components/Layout/const' import {ScrollbarOffsetContext} from '#/components/Layout/context' import {Text} from '#/components/Typography' +import {useAnalytics} from '#/analytics' import {IS_IOS} from '#/env' export function Outer({ @@ -46,6 +49,7 @@ export function Outer({ const {gtMobile} = useBreakpoints() const {isWithinOffsetView} = useContext(ScrollbarOffsetContext) const {centerColumnOffset} = useLayoutBreakpoints() + const {isWithinSplitView, isWithinLeftPanel} = useIsWithinSplitView() return ( {children} @@ -146,13 +151,15 @@ export function BackButton({onPress, style, ...props}: Partial) { export function MenuButton() { const {_} = useLingui() + const ax = useAnalytics() const setDrawerOpen = useSetDrawerOpen() const {gtMobile} = useBreakpoints() const onPress = useCallback(() => { + ax.metric('nav:click', {item: 'menu', surface: 'topBar'}) Keyboard.dismiss() setDrawerOpen(true) - }, [setDrawerOpen]) + }, [setDrawerOpen, ax]) return gtMobile ? null : ( @@ -179,12 +186,14 @@ export function TitleText({ style, }: {children: React.ReactNode} & TextStyleProp) { const {gtMobile} = useBreakpoints() + const {isWithinLeftPanel} = useIsWithinSplitView() const align = useContext(AlignmentContext) return ( - {IS_WEB && } + {IS_WEB && !isWithinSplitView && } @@ -85,14 +91,15 @@ export const Content = memo( ) { const t = useTheme() const {footerHeight} = useShellLayout() - const animatedProps = useAnimatedProps(() => { + const {isWithinSplitView} = useIsWithinSplitView() + + // note - if we ever make the footer transparent in any way, + // we'll need to change this to use contentInsets/scrollIndicatorInsets + // on iOS and contentContainerStyle padding on Android -sfn + const animatedStyle = useAnimatedStyle(() => { return { - scrollIndicatorInsets: { - bottom: footerHeight.get(), - top: 0, - right: 1, - }, - } satisfies AnimatedScrollViewProps + marginBottom: footerHeight.get(), + } }) return ( @@ -101,13 +108,19 @@ export const Content = memo( id="content" automaticallyAdjustsScrollIndicatorInsets={false} indicatorStyle={t.scheme === 'dark' ? 'white' : 'black'} - // sets the scroll inset to the height of the footer - animatedProps={animatedProps} - style={[scrollViewStyles.common, style]} - contentContainerStyle={[ - scrollViewStyles.contentContainer, - contentContainerStyle, + style={[ + a.w_full, + animatedStyle, + isWithinSplitView && + web({ + flex: 1, + overflowY: 'scroll', + scrollbarWidth: 'thin', + scrollbarColor: `${t.palette.contrast_100} transparent`, + }), + style, ]} + contentContainerStyle={[contentContainerStyle]} {...props}> {IS_WEB ? (
@@ -122,45 +135,6 @@ export const Content = memo( }), ) -const scrollViewStyles = StyleSheet.create({ - common: { - width: '100%', - }, - contentContainer: { - paddingBottom: 100, - }, -}) - -export type KeyboardAwareContentProps = KeyboardAwareScrollViewProps & { - children: React.ReactNode - contentContainerStyle?: StyleProp -} - -/** - * Default scroll view for simple pages. - * - * BE SURE TO TEST THIS WHEN USING, it's untested as of writing this comment. - */ -export const KeyboardAwareContent = memo(function LayoutKeyboardAwareContent({ - children, - style, - contentContainerStyle, - ...props -}: KeyboardAwareContentProps) { - return ( - - {IS_WEB ?
{children}
: children} -
- ) -}) - /** * Utility component to center content within the screen */ @@ -174,28 +148,30 @@ export const Center = memo(function LayoutCenter({ const {gtMobile} = useBreakpoints() const {centerColumnOffset} = useLayoutBreakpoints() const {isWithinDialog} = useDialogContext() + const {isWithinSplitView} = useIsWithinSplitView() const ctx = useMemo(() => ({isWithinOffsetView: true}), []) return ( diff --git a/src/view/com/lightbox/Lightbox.tsx b/src/components/Lightbox/Lightbox.tsx similarity index 67% rename from src/view/com/lightbox/Lightbox.tsx rename to src/components/Lightbox/Lightbox.tsx index 003d01a94a..d5f864fe30 100644 --- a/src/view/com/lightbox/Lightbox.tsx +++ b/src/components/Lightbox/Lightbox.tsx @@ -2,8 +2,8 @@ import {useCallback} from 'react' import {shareImageModal} from '#/lib/media/manip' import {useSaveImageToMediaLibrary} from '#/lib/media/save-image' -import {useLightbox, useLightboxControls} from '#/state/lightbox' -import ImageView from './ImageViewing' +import ImageView from '#/components/Lightbox/pager/ImagePager' +import {useLightbox, useLightboxControls} from '#/components/Lightbox/state' export function Lightbox() { const {activeLightbox} = useLightbox() @@ -19,8 +19,8 @@ export function Lightbox() { shareImageModal({uri})} + onPressSave={uri => void saveImageToAlbum(uri)} + onPressShare={uri => void shareImageModal({uri})} /> ) } diff --git a/src/view/com/lightbox/Lightbox.web.tsx b/src/components/Lightbox/Lightbox.web.tsx similarity index 71% rename from src/view/com/lightbox/Lightbox.web.tsx rename to src/components/Lightbox/Lightbox.web.tsx index ddfc4fd8b7..0222aa3156 100644 --- a/src/view/com/lightbox/Lightbox.web.tsx +++ b/src/components/Lightbox/Lightbox.web.tsx @@ -1,15 +1,12 @@ import {useCallback, useEffect, useRef, useState} from 'react' -import {Pressable, StyleSheet, View} from 'react-native' +import {Pressable, ScrollView, StyleSheet, View} from 'react-native' import {Image} from 'expo-image' -import {msg} from '@lingui/core/macro' -import {useLingui} from '@lingui/react' -import {Trans} from '@lingui/react/macro' +import {Trans, useLingui} from '@lingui/react/macro' import {FocusGuards, FocusScope} from 'radix-ui/internal' import {RemoveScrollBar} from 'react-remove-scroll-bar' import {saveImageToMediaLibrary} from '#/lib/media/manip' import {useA11y} from '#/state/a11y' -import {useLightbox, useLightboxControls} from '#/state/lightbox' import { atoms as a, flatten, @@ -19,6 +16,7 @@ import { } from '#/alf' import {Button} from '#/components/Button' import {Backdrop} from '#/components/Dialog' +import {ArrowOutOfBox_Stroke2_Corner0_Rounded as ShareIcon} from '#/components/icons/ArrowOutOfBox' import { ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeftIcon, ChevronRight_Stroke2_Corner0_Rounded as ChevronRightIcon, @@ -26,11 +24,14 @@ import { import {DotGrid3x1_Stroke2_Corner0_Rounded as EllipsisIcon} from '#/components/icons/DotGrid' import {Download_Stroke2_Corner0_Rounded as DownloadIcon} from '#/components/icons/Download' import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' +import {CircleChromeButton} from '#/components/Lightbox/chrome/CircleChromeButton' +import {PagerDots} from '#/components/Lightbox/chrome/PagerDots' +import {useLightbox, useLightboxControls} from '#/components/Lightbox/state' +import {type ImageSource} from '#/components/Lightbox/types' import {Loader} from '#/components/Loader' import * as Menu from '#/components/Menu' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' -import {type ImageSource} from './ImageViewing/@types' export function Lightbox() { const {activeLightbox} = useLightbox() @@ -64,12 +65,12 @@ function LightboxContainer({ children: React.ReactNode handleBackgroundPress: () => void }) { - const {_} = useLingui() + const {t: l} = useLingui() FocusGuards.useFocusGuards() return ( @@ -78,7 +79,7 @@ function LightboxContainer({
{children}
@@ -97,7 +98,7 @@ function LightboxGallery({ onClose: () => void }) { const t = useTheme() - const {_} = useLingui() + const {t: l} = useLingui() const {reduceMotionEnabled} = useA11y() const [index, setIndex] = useState(initialIndex) const [hasAnyLoaded, setAnyHasLoaded] = useState(false) @@ -193,7 +194,7 @@ function LightboxGallery({ ]} hoverStyle={styles.blurredBackdropHover} color="secondary" - label={_(msg`Previous image`)} + label={l`Previous image`} shape="round" size={gtPhone ? 'large' : 'small'}> {img.alt ? ( - + { setAltExpanded(!isAltExpanded) }}> {img.alt} - + ) : null} {imgs.length > 1 && (
- {_(msg`Image ${index + 1} of ${imgs.length}`)} + {l`Image ${index + 1} of ${imgs.length}`}
)} - + {({props}) => ( - +
)} { + const url = img.uri + if ( + typeof navigator !== 'undefined' && + 'share' in navigator && + navigator.share + ) { + try { + await navigator.share({url}) + } catch { + // User cancelled or share failed; no-op + } + } else if ( + typeof navigator !== 'undefined' && + navigator.clipboard + ) { + try { + await navigator.clipboard.writeText(url) + Toast.show(l`Link copied to clipboard`) + } catch { + Toast.show(l`Failed to copy link`, {type: 'error'}) + } + } + }}> + + Share image + + + + { saveImageToMediaLibrary({uri: img.uri}).then( () => { - Toast.show(_(msg`Image saved`)) + Toast.show(l`Image saved`) }, () => { - Toast.show(_(msg`Failed to save image`), {type: 'error'}) + Toast.show(l`Failed to save image`, {type: 'error'}) }, ) }}> @@ -294,22 +328,20 @@ function LightboxGallery({ - + + + + {imgs.length > 1 && ( + + + + )} ) } @@ -421,6 +453,14 @@ const styles = StyleSheet.create({ padding: 16, boxSizing: 'border-box', }, + altScroll: { + // Size to content like the View it replaced, rather than filling the + // column via ScrollView's default flexGrow. + flexGrow: 0, + flexShrink: 0, + // @ts-ignore web-only -sfn + maxHeight: '50vh', + }, menuBtn: { top: 20, left: 20, @@ -429,6 +469,14 @@ const styles = StyleSheet.create({ top: 20, right: 20, }, + pagerDots: { + top: 20, + left: 0, + right: 0, + height: 44, + alignItems: 'center', + justifyContent: 'center', + }, leftBtn: { left: 20, right: 'auto', diff --git a/src/components/Lightbox/chrome/CircleChromeButton.tsx b/src/components/Lightbox/chrome/CircleChromeButton.tsx new file mode 100644 index 0000000000..9b31266bd3 --- /dev/null +++ b/src/components/Lightbox/chrome/CircleChromeButton.tsx @@ -0,0 +1,75 @@ +import {type ComponentType} from 'react' +import { + Pressable, + type PressableProps, + type StyleProp, + StyleSheet, + type TextStyle, +} from 'react-native' +import {BlurView} from 'expo-blur' + +import {HITSLOP_20} from '#/lib/constants' +import {type Props as IconProps} from '#/components/icons/common' + +type Props = { + icon: ComponentType + iconStyle?: StyleProp + label: string + onPress?: PressableProps['onPress'] + testID?: string +} & Omit< + PressableProps, + | 'onPress' + | 'style' + | 'testID' + | 'accessibilityRole' + | 'accessibilityLabel' + | 'accessibilityHint' +> + +const SIZE = 32 +const RADIUS = SIZE / 2 +const ICON = 18 + +export function CircleChromeButton({ + icon: Icon, + iconStyle, + label, + onPress, + testID, + ...rest +}: Props) { + return ( + [styles.root, pressed && styles.pressed]}> + + + + + ) +} + +const styles = StyleSheet.create({ + root: { + width: SIZE, + height: SIZE, + borderRadius: RADIUS, + overflow: 'hidden', + }, + inner: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: 'rgba(0, 0, 0, 0.5)', + }, + pressed: { + opacity: 0.85, + }, +}) diff --git a/src/components/Lightbox/chrome/CircleChromeButton.web.tsx b/src/components/Lightbox/chrome/CircleChromeButton.web.tsx new file mode 100644 index 0000000000..5e8922c8c3 --- /dev/null +++ b/src/components/Lightbox/chrome/CircleChromeButton.web.tsx @@ -0,0 +1,78 @@ +import {type ComponentType} from 'react' +import { + Pressable, + type PressableProps, + type StyleProp, + StyleSheet, + type TextStyle, + View, +} from 'react-native' + +import {HITSLOP_10} from '#/lib/constants' +import {type Props as IconProps} from '#/components/icons/common' + +type Props = { + icon: ComponentType + iconStyle?: StyleProp + label: string + onPress?: PressableProps['onPress'] + testID?: string +} & Omit< + PressableProps, + | 'onPress' + | 'style' + | 'testID' + | 'accessibilityRole' + | 'accessibilityLabel' + | 'accessibilityHint' +> + +const SIZE = 44 +const RADIUS = 24 +const ICON = 24 + +export function CircleChromeButton({ + icon: Icon, + iconStyle, + label, + onPress, + testID, + ...rest +}: Props) { + return ( + [styles.root, pressed && styles.pressed]}> + + + + + ) +} + +const styles = StyleSheet.create({ + root: { + width: SIZE, + height: SIZE, + borderRadius: RADIUS, + overflow: 'hidden', + }, + inner: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: 'rgba(0, 0, 0, 0.75)', + // @ts-expect-error web-only + backdropFilter: 'blur(8px)', + WebkitBackdropFilter: 'blur(8px)', + }, + pressed: { + opacity: 0.85, + }, +}) diff --git a/src/components/Lightbox/chrome/Footer.tsx b/src/components/Lightbox/chrome/Footer.tsx new file mode 100644 index 0000000000..0a708c7545 --- /dev/null +++ b/src/components/Lightbox/chrome/Footer.tsx @@ -0,0 +1,114 @@ +import {useRef} from 'react' +import {LayoutAnimation, ScrollView, StyleSheet, View} from 'react-native' +import { + useSafeAreaFrame, + useSafeAreaInsets, +} from 'react-native-safe-area-context' +import {BlurView} from 'expo-blur' +import {useLingui} from '@lingui/react/macro' + +import {atoms as a, platform, useTheme} from '#/alf' +import {Text} from '#/components/Typography' + +type Props = { + altText: string | undefined + isAltExpanded: boolean + onToggleAltExpanded: () => void +} + +export function Footer({altText, isAltExpanded, onToggleAltExpanded}: Props) { + const {t: l} = useLingui() + const t = useTheme() + const insets = useSafeAreaInsets() + const {height: screenHeight} = useSafeAreaFrame() + const isMomentumScrolling = useRef(false) + + if (!altText) return null + + // Cap the overlay height so long alt text - or text enlarged by the OS via + // Dynamic Type / font scaling - scrolls within the overlay instead of growing + // past the top of the screen. Leaves the upper half clear for the header. + const maxHeight = screenHeight / 2 + + return ( + + + + { + isMomentumScrolling.current = true + }} + onMomentumScrollEnd={() => { + isMomentumScrolling.current = false + }} + contentContainerStyle={[a.px_md, a.py_sm]}> + + {/* + * The press handlers must live on the Text itself, not on a + * wrapping Pressable. Text selection is driven by the platform's + * native text view long-press (RN Text on Android, UITextView on + * iOS). A parent touchable consumes that long-press before the + * selectable Text can begin a selection - on Android this prevents + * selection entirely. Keeping onPress/onLongPress on the Text lets + * the same native node own both the tap-to-expand and the + * long-press-to-select. The empty onLongPress is intentional: it + * reserves the long-press for the OS selection gesture instead of + * firing the expand toggle. RN exposes no API to arbitrate tap vs + * native selection on a single node, so this is the supported + * workaround, and it behaves consistently on both platforms. + */} + { + if (isMomentumScrolling.current) return + LayoutAnimation.configureNext({ + duration: 450, + update: {type: 'spring', springDamping: 1}, + }) + onToggleAltExpanded() + }} + onLongPress={() => {}}> + {altText} + + + + + + + ) +} + +const styles = StyleSheet.create({ + altWrap: { + borderRadius: 12, + overflow: 'hidden', + }, +}) diff --git a/src/components/Lightbox/chrome/Header.tsx b/src/components/Lightbox/chrome/Header.tsx new file mode 100644 index 0000000000..98dff40ba4 --- /dev/null +++ b/src/components/Lightbox/chrome/Header.tsx @@ -0,0 +1,52 @@ +import {View} from 'react-native' +import {useSafeAreaInsets} from 'react-native-safe-area-context' +import {useLingui} from '@lingui/react/macro' + +import {atoms as a} from '#/alf' +import {TimesLarge_Stroke2_Corner0_Rounded as CloseIcon} from '#/components/icons/Times' +import {CircleChromeButton} from './CircleChromeButton' +import {ImageMenu} from './ImageMenu' +import {PagerDots} from './PagerDots' + +type Props = { + onRequestClose: () => void + onPressShare: () => void + onPressSave: () => void + imageCount: number + activeIndex: number +} + +export function Header({ + onRequestClose, + onPressShare, + onPressSave, + imageCount, + activeIndex, +}: Props) { + const {t: l} = useLingui() + const insets = useSafeAreaInsets() + + return ( + + + + + + ) +} diff --git a/src/components/Lightbox/chrome/ImageMenu.tsx b/src/components/Lightbox/chrome/ImageMenu.tsx new file mode 100644 index 0000000000..92b5c47749 --- /dev/null +++ b/src/components/Lightbox/chrome/ImageMenu.tsx @@ -0,0 +1,192 @@ +import {useRef, useState} from 'react' +import {Modal, Pressable, StyleSheet, View} from 'react-native' +import Animated, { + interpolate, + runOnJS, + useAnimatedStyle, + useSharedValue, + withSpring, + withTiming, +} from 'react-native-reanimated' +import {useLingui} from '@lingui/react/macro' + +import {atoms as a} from '#/alf' +import {ArrowShareRight_Stroke2_Corner2_Rounded as ShareIcon} from '#/components/icons/ArrowShareRight' +import {type Props as IconProps} from '#/components/icons/common' +import {DotGrid3x1_Stroke2_Corner0_Rounded as DotsIcon} from '#/components/icons/DotGrid' +import {Download_Stroke2_Corner0_Rounded as DownloadIcon} from '#/components/icons/Download' +import {Text} from '#/components/Typography' +import {CircleChromeButton} from './CircleChromeButton' + +type Props = { + onPressShare: () => void + onPressSave: () => void +} + +type Anchor = {x: number; y: number; width: number; height: number} + +const GAP = 6 +const CARD_BG = '#000000' +const CARD_BORDER = '#232e3e' +const ITEM_TEXT = '#f9fafb' + +const SPRING_IN = {damping: 18, mass: 0.6, stiffness: 240} +const TIMING_OUT = {duration: 150} + +export function ImageMenu({onPressShare, onPressSave}: Props) { + const {t: l} = useLingui() + const triggerRef = useRef(null) + const [isMounted, setIsMounted] = useState(false) + const [anchor, setAnchor] = useState(null) + const progress = useSharedValue(0) + + const open = () => { + triggerRef.current?.measureInWindow((x, y, width, height) => { + setAnchor({x, y, width, height}) + setIsMounted(true) + progress.set(withSpring(1, SPRING_IN)) + }) + } + + const close = () => { + progress.set( + withTiming(0, TIMING_OUT, finished => { + if (finished) { + runOnJS(setIsMounted)(false) + } + }), + ) + } + + const runAction = (action: () => void) => { + close() + action() + } + + return ( + <> + + + + + + {anchor && ( + + runAction(onPressShare)} + /> + runAction(onPressSave)} + /> + + )} + + + ) +} + +function MenuCard({ + anchor, + progress, + children, +}: { + anchor: Anchor + progress: ReturnType> + children: React.ReactNode +}) { + const animatedStyle = useAnimatedStyle(() => ({ + opacity: progress.get(), + transform: [{scale: interpolate(progress.get(), [0, 1], [0.9, 1])}], + })) + + return ( + + {children} + + ) +} + +function MenuItem({ + icon: Icon, + label, + onPress, +}: { + icon: React.ComponentType + label: string + onPress: () => void +}) { + return ( + [styles.item, pressed && styles.itemPressed]}> + + {label} + + ) +} + +const styles = StyleSheet.create({ + card: { + padding: 8, + borderRadius: 16, + borderWidth: 0.5, + borderColor: CARD_BORDER, + backgroundColor: CARD_BG, + shadowColor: '#000', + shadowOffset: {width: 0, height: 4}, + shadowOpacity: 0.16, + shadowRadius: 20, + elevation: 8, + }, + item: { + height: 44, + paddingHorizontal: 12, + borderRadius: 8, + flexDirection: 'row', + alignItems: 'center', + gap: 6, + }, + itemPressed: { + backgroundColor: 'rgba(255, 255, 255, 0.08)', + }, + itemText: { + fontSize: 15, + fontWeight: '500', + lineHeight: 19.5, + color: ITEM_TEXT, + }, +}) diff --git a/src/components/Lightbox/chrome/PagerDots.tsx b/src/components/Lightbox/chrome/PagerDots.tsx new file mode 100644 index 0000000000..ea3cbc7f17 --- /dev/null +++ b/src/components/Lightbox/chrome/PagerDots.tsx @@ -0,0 +1,65 @@ +import {StyleSheet, View} from 'react-native' +import {BlurView} from 'expo-blur' + +type Props = { + count: number + activeIndex: number +} + +const ACTIVE = 6 +const INACTIVE = 4 +const GAP = 5 + +export function PagerDots({count, activeIndex}: Props) { + if (count <= 1) return null + return ( + + + {Array.from({length: count}).map((_, i) => { + const isActive = i === activeIndex + return ( + + ) + })} + + + ) +} + +const styles = StyleSheet.create({ + root: { + borderRadius: 999, + overflow: 'hidden', + }, + inner: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: GAP, + paddingHorizontal: 10, + paddingVertical: 6, + backgroundColor: 'rgba(0, 0, 0, 0.5)', + }, + activeDot: { + width: ACTIVE, + height: ACTIVE, + borderRadius: ACTIVE / 2, + }, + inactiveDot: { + width: INACTIVE, + height: INACTIVE, + borderRadius: INACTIVE / 2, + }, + active: { + backgroundColor: '#fff', + }, + inactive: { + backgroundColor: 'rgba(255, 255, 255, 0.4)', + }, +}) diff --git a/src/components/Lightbox/chrome/PagerDots.web.tsx b/src/components/Lightbox/chrome/PagerDots.web.tsx new file mode 100644 index 0000000000..370917254f --- /dev/null +++ b/src/components/Lightbox/chrome/PagerDots.web.tsx @@ -0,0 +1,62 @@ +import {StyleSheet, View} from 'react-native' + +type Props = { + count: number + activeIndex: number +} + +const ACTIVE = 6 +const INACTIVE = 4 +const GAP = 5 + +export function PagerDots({count, activeIndex}: Props) { + if (count <= 1) return null + return ( + + {Array.from({length: count}).map((_, i) => { + const isActive = i === activeIndex + return ( + + ) + })} + + ) +} + +const styles = StyleSheet.create({ + root: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: GAP, + paddingHorizontal: 10, + paddingVertical: 6, + borderRadius: 999, + backgroundColor: 'rgba(0, 0, 0, 0.75)', + // @ts-expect-error web-only + backdropFilter: 'blur(8px)', + WebkitBackdropFilter: 'blur(8px)', + }, + activeDot: { + width: ACTIVE, + height: ACTIVE, + borderRadius: ACTIVE / 2, + }, + inactiveDot: { + width: INACTIVE, + height: INACTIVE, + borderRadius: INACTIVE / 2, + }, + active: { + backgroundColor: '#fff', + }, + inactive: { + backgroundColor: 'rgba(255, 255, 255, 0.4)', + }, +}) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx new file mode 100644 index 0000000000..ef6d5fb318 --- /dev/null +++ b/src/components/Lightbox/index.tsx @@ -0,0 +1 @@ +export {Lightbox} from './Lightbox' diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx b/src/components/Lightbox/pager/ImageItem/ImageItem.android.tsx similarity index 99% rename from src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx rename to src/components/Lightbox/pager/ImageItem/ImageItem.android.tsx index 9d128a41cf..62e28523a7 100644 --- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx +++ b/src/components/Lightbox/pager/ImageItem/ImageItem.android.tsx @@ -6,6 +6,7 @@ import { type PanGesture, } from 'react-native-gesture-handler' import Animated, { + type AnimatableValue, runOnJS, type SharedValue, useAnimatedReaction, @@ -20,7 +21,7 @@ import { type Dimensions as ImageDimensions, type ImageSource, type LightboxTransforms, -} from '../../@types' +} from '../../types' import { applyRounding, createTransform, @@ -29,7 +30,7 @@ import { prependTransform, readTransform, type TransformMatrix, -} from '../../transforms' +} from '../transforms' const MIN_SCREEN_ZOOM = 2 const MAX_ORIGINAL_IMAGE_ZOOM = 2 @@ -460,7 +461,7 @@ function clampTranslation( return clampedValue } -function withClampedSpring(value: any) { +function withClampedSpring(value: T): T { 'worklet' return withSpring(value, {overshootClamping: true}) } diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx b/src/components/Lightbox/pager/ImageItem/ImageItem.ios.tsx similarity index 99% rename from src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx rename to src/components/Lightbox/pager/ImageItem/ImageItem.ios.tsx index 52173e057a..19373cbe1a 100644 --- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx +++ b/src/components/Lightbox/pager/ImageItem/ImageItem.ios.tsx @@ -30,7 +30,7 @@ import { type Dimensions as ImageDimensions, type ImageSource, type LightboxTransforms, -} from '../../@types' +} from '../../types' const MAX_ORIGINAL_IMAGE_ZOOM = 2 const MIN_SCREEN_ZOOM = 2 @@ -246,6 +246,7 @@ const ImageItem = ({ } } cachePolicy="memory" + useAppleWebpCodec /> diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx b/src/components/Lightbox/pager/ImageItem/ImageItem.tsx similarity index 97% rename from src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx rename to src/components/Lightbox/pager/ImageItem/ImageItem.tsx index cd0e45d3c5..513c20f6af 100644 --- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx +++ b/src/components/Lightbox/pager/ImageItem/ImageItem.tsx @@ -10,7 +10,7 @@ import { type Dimensions as ImageDimensions, type ImageSource, type LightboxTransforms, -} from '../../@types' +} from '../../types' type Props = { imageSrc: ImageSource diff --git a/src/view/com/lightbox/ImageViewing/index.tsx b/src/components/Lightbox/pager/ImagePager.tsx similarity index 82% rename from src/view/com/lightbox/ImageViewing/index.tsx rename to src/components/Lightbox/pager/ImagePager.tsx index 40cabd722d..c960490e91 100644 --- a/src/view/com/lightbox/ImageViewing/index.tsx +++ b/src/components/Lightbox/pager/ImagePager.tsx @@ -7,18 +7,13 @@ */ // Original code copied and simplified from the link below as the codebase is currently not maintained: // https://github.com/jobtoday/react-native-image-viewing -import {useCallback, useEffect, useMemo, useRef, useState} from 'react' -import { - LayoutAnimation, - PixelRatio, - StyleSheet, - useWindowDimensions, - View, -} from 'react-native' +import {useCallback, useEffect, useMemo, useState} from 'react' +import {PixelRatio, StyleSheet, useWindowDimensions, View} from 'react-native' import {SystemBars} from 'react-native-edge-to-edge' import {Gesture} from 'react-native-gesture-handler' import PagerView from 'react-native-pager-view' import Animated, { + type AnimatableValue, type AnimatedRef, cancelAnimation, interpolate, @@ -37,28 +32,24 @@ import Animated, { withSpring, type WithSpringConfig, } from 'react-native-reanimated' -import {SafeAreaView} from 'react-native-safe-area-context' +import {Image} from 'expo-image' import * as ScreenOrientation from 'expo-screen-orientation' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {Trans} from '@lingui/react/macro' import {type Dimensions} from '#/lib/media/types' -import {colors, s} from '#/lib/styles' -import {type Lightbox} from '#/state/lightbox' -import {Button} from '#/view/com/util/forms/Button' -import {Text} from '#/view/com/util/text/Text' -import {ScrollView} from '#/view/com/util/Views' import {useTheme} from '#/alf' import {setSystemUITheme} from '#/alf/util/systemUI' +import {type Lightbox} from '#/components/Lightbox/state' +import {useAnalytics} from '#/analytics' import {IS_IOS} from '#/env' -import {PlatformInfo} from '../../../../../modules/expo-bluesky-swiss-army' +import {PlatformInfo} from '../../../../modules/expo-bluesky-swiss-army' +import {Footer} from '../chrome/Footer' +import {Header} from '../chrome/Header' import { type ImageSource, type LightboxTransforms, type Transform, -} from './@types' -import ImageDefaultHeader from './components/ImageDefaultHeader' -import ImageItem from './components/ImageItem/ImageItem' +} from '../types' +import ImageItem from './ImageItem/ImageItem' type Rect = {x: number; y: number; width: number; height: number} @@ -100,6 +91,9 @@ export default function ImageViewRoot({ 'use no memo' const ref = useAnimatedRef() const [activeLightbox, setActiveLightbox] = useState(nextLightbox) + // Lives here rather than in ImageView so it survives the remount + // when the orientation-based key below changes on rotation. + const [imageIndex, setImageIndex] = useState(nextLightbox?.index ?? 0) const [orientation, setOrientation] = useState<'portrait' | 'landscape'>( 'portrait', ) @@ -110,6 +104,7 @@ export default function ImageViewRoot({ if (!activeLightbox && nextLightbox) { setActiveLightbox(nextLightbox) + setImageIndex(nextLightbox.index) } useEffect(() => { @@ -147,6 +142,9 @@ export default function ImageViewRoot({ 'worklet' thumbRects.set({}) })() + requestIdleCallback(() => { + void Image.clearMemoryCache() + }) }, [thumbRects]) useAnimatedReaction( @@ -199,6 +197,8 @@ export default function ImageViewRoot({ > orientation: 'portrait' | 'landscape' onRequestClose: () => void onPressSave: (uri: string) => void @@ -235,11 +239,14 @@ function ImageView({ openProgress: SharedValue thumbRects: SharedValue> }) { - const {images, index: initialImageIndex} = lightbox + const {images, metricsContext} = lightbox + // Capture at mount: after a rotation remount this is the preserved + // current index, so the pager re-opens on the same image. + const [initialImageIndex] = useState(imageIndex) + const ax = useAnalytics() const isAnimated = useMemo(() => canAnimate(lightbox), [lightbox]) const [isScaled, setIsScaled] = useState(false) const [isDragging, setIsDragging] = useState(false) - const [imageIndex, setImageIndex] = useState(initialImageIndex) const [showControls, setShowControls] = useState(true) const [isAltExpanded, setIsAltExpanded] = useState(false) const dismissSwipeTranslateY = useSharedValue(0) @@ -384,7 +391,21 @@ function ImageView({ scrollEnabled={!isScaled} initialPage={initialImageIndex} onPageSelected={e => { - setImageIndex(e.nativeEvent.position) + const next = e.nativeEvent.position + setImageIndex(prev => { + if (metricsContext && prev !== next) { + ax.metric('post:photoEmbed:lightboxSwipe', { + layout: metricsContext.layout, + fromImage: prev + 1, + toImage: next + 1, + totalImages: images.length, + postUri: metricsContext.postUri, + postAuthorDid: metricsContext.postAuthorDid, + feedDescriptor: metricsContext.feedDescriptor, + }) + } + return next + }) setIsScaled(false) }} onPageScrollStateChanged={e => { @@ -393,7 +414,7 @@ function ImageView({ overdrag={true} style={styles.pager}> {images.map((imageSrc, i) => ( - + ))} - + - +
onPressShare(images[imageIndex].uri)} + onPressSave={() => onPressSave(images[imageIndex].uri)} + imageCount={images.length} + activeIndex={imageIndex} + /> - setIsAltExpanded(e => !e)} - onPressSave={onPressSave} - onPressShare={onPressShare} + onToggleAltExpanded={() => setIsAltExpanded(e => !e)} /> @@ -607,85 +633,6 @@ function LightboxImage({ ) } -function LightboxFooter({ - images, - index, - isAltExpanded, - toggleAltExpanded, - onPressSave, - onPressShare, -}: { - images: ImageSource[] - index: number - isAltExpanded: boolean - toggleAltExpanded: () => void - onPressSave: (uri: string) => void - onPressShare: (uri: string) => void -}) { - const {alt: altText, uri} = images[index] - const isMomentumScrolling = useRef(false) - return ( - { - isMomentumScrolling.current = true - }} - onMomentumScrollEnd={() => { - isMomentumScrolling.current = false - }} - contentContainerStyle={{ - paddingVertical: 12, - paddingHorizontal: 24, - }}> - - {altText ? ( - - { - if (isMomentumScrolling.current) { - return - } - LayoutAnimation.configureNext({ - duration: 450, - update: {type: 'spring', springDamping: 1}, - }) - toggleAltExpanded() - }} - onLongPress={() => {}} - emoji> - {altText} - - - ) : null} - - - - - - - ) -} - const styles = StyleSheet.create({ screen: { position: 'absolute', @@ -722,41 +669,6 @@ const styles = StyleSheet.create({ pager: { flex: 1, }, - header: { - position: 'absolute', - width: '100%', - top: 0, - pointerEvents: 'box-none', - }, - footer: { - position: 'absolute', - width: '100%', - maxHeight: '100%', - bottom: 0, - }, - footerScrollView: { - backgroundColor: '#000d', - flex: 1, - position: 'absolute', - bottom: 0, - width: '100%', - maxHeight: '100%', - }, - footerText: { - paddingBottom: IS_IOS ? 20 : 16, - }, - footerBtns: { - flexDirection: 'row', - justifyContent: 'center', - gap: 8, - }, - footerBtn: { - flexDirection: 'row', - alignItems: 'center', - gap: 8, - backgroundColor: 'transparent', - borderColor: colors.white, - }, }) function interpolatePx( @@ -856,7 +768,10 @@ function interpolateTransform( } } -function withClampedSpring(value: any, config: WithSpringConfig) { +function withClampedSpring( + value: T, + config: WithSpringConfig, +): T { 'worklet' return withSpring(value, {...config, overshootClamping: true}) } diff --git a/src/view/com/lightbox/ImageViewing/transforms.ts b/src/components/Lightbox/pager/transforms.ts similarity index 98% rename from src/view/com/lightbox/ImageViewing/transforms.ts rename to src/components/Lightbox/pager/transforms.ts index ae6e4aed80..25b826995f 100644 --- a/src/view/com/lightbox/ImageViewing/transforms.ts +++ b/src/components/Lightbox/pager/transforms.ts @@ -1,4 +1,4 @@ -import {type Position} from './@types' +import {type Position} from '../types' export type TransformMatrix = [ number, diff --git a/src/state/lightbox.tsx b/src/components/Lightbox/state.tsx similarity index 88% rename from src/state/lightbox.tsx rename to src/components/Lightbox/state.tsx index 7d688dab20..6a8fdbe0b7 100644 --- a/src/state/lightbox.tsx +++ b/src/components/Lightbox/state.tsx @@ -9,12 +9,22 @@ import {nanoid} from 'nanoid/non-secure' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {useHotkeysContext} from '#/lib/hotkeys' -import {type ImageSource} from '#/view/com/lightbox/ImageViewing/@types' +import {type ImageSource} from '#/components/Lightbox/types' + +export type LightboxMetricsContext = { + layout: 'single' | 'grid' | 'carousel' + postUri: string + postAuthorDid: string + feedDescriptor?: string +} export type Lightbox = { id: string images: ImageSource[] index: number + // Set for post photo embeds so the lightbox can emit post:photoEmbed:lightboxSwipe. + // Left unset for non-post contexts (e.g. profile avatar/banner lightbox). + metricsContext?: LightboxMetricsContext } const LightboxContext = createContext<{ diff --git a/src/view/com/lightbox/ImageViewing/@types/index.ts b/src/components/Lightbox/types.ts similarity index 92% rename from src/view/com/lightbox/ImageViewing/@types/index.ts rename to src/components/Lightbox/types.ts index c3aca3d859..19789fdc62 100644 --- a/src/view/com/lightbox/ImageViewing/@types/index.ts +++ b/src/components/Lightbox/types.ts @@ -6,6 +6,7 @@ * */ +import {type Component} from 'react' import {type TransformsStyle} from 'react-native' import { type AnimatedRef, @@ -28,7 +29,7 @@ export type ImageSource = { thumbUri: string thumbDimensions: Dimensions | null thumbRect: MeasuredDimensions | null - thumbRef?: AnimatedRef | null + thumbRef?: AnimatedRef | null thumbBorderRadius?: number alt?: string type: 'image' | 'circle-avi' | 'rect-avi' diff --git a/src/components/Link.tsx b/src/components/Link.tsx index 7d55a5f48b..258ba58ca4 100644 --- a/src/components/Link.tsx +++ b/src/components/Link.tsx @@ -1,12 +1,19 @@ import {useCallback, useMemo} from 'react' -import {type GestureResponderEvent, Linking} from 'react-native' +import { + type GestureResponderEvent, + Linking, + type NativeSyntheticEvent, + type TargetedEvent, +} from 'react-native' import {sanitizeUrl} from '@braintree/sanitize-url' +import {useLingui} from '@lingui/react/macro' import { type LinkProps as RNLinkProps, StackActions, } from '@react-navigation/native' import {BSKY_DOWNLOAD_URL} from '#/lib/constants' +import {useGroupChatJoinIntent} from '#/lib/hooks/useIntentHandler' import {useNavigationDeduped} from '#/lib/hooks/useNavigationDeduped' import {useOpenLink} from '#/lib/hooks/useOpenLink' import {type AllNavigatorParams, type RouteParams} from '#/lib/routes/types' @@ -14,16 +21,20 @@ import {shareUrl} from '#/lib/sharing' import { convertBskyAppUrlIfNeeded, createProxiedUrl, + getChatInviteCodeFromUrl, isBskyDownloadUrl, isExternalUrl, linkRequiresWarning, } from '#/lib/strings/url-helpers' import {useModalControls} from '#/state/modals' +import {useInAppBrowser} from '#/state/preferences/in-app-browser' import {atoms as a, flatten, type TextStyleProp, useTheme, web} from '#/alf' import {Button, type ButtonProps} from '#/components/Button' import {useInteractionState} from '#/components/hooks/useInteractionState' +import {ArrowShareRight_Stroke2_Corner2_Rounded as ShareIcon} from '#/components/icons/ArrowShareRight' +import * as PeekMenu from '#/components/PeekMenu' import {Text, type TextProps} from '#/components/Typography' -import {IS_NATIVE, IS_WEB} from '#/env' +import {IS_IOS, IS_NATIVE, IS_WEB} from '#/env' import {router} from '#/routes' import {useGlobalDialogsControlContext} from './dialogs/Context' @@ -78,6 +89,22 @@ type BaseLinkProps = { * Whether the link should be opened through the redirect proxy. */ shouldProxy?: boolean + + /** + * iOS only. Wraps the link in a peek menu: long-pressing previews the live + * page in an in-app browser (morphing into it on tap when the in-app browser + * preference is on), with a share action in the menu. No-op elsewhere. + */ + peek?: boolean + + /** + * Web only + */ + onMouseEnter?: () => void + /** + * Web only + */ + onMouseLeave?: () => void } export function useLink({ @@ -116,6 +143,7 @@ export function useLink({ const {closeModal} = useModalControls() const {linkWarningDialogControl} = useGlobalDialogsControlContext() const openLink = useOpenLink() + const groupChatJoinIntent = useGroupChatJoinIntent() const onPress = useCallback( (e: GestureResponderEvent) => { @@ -125,15 +153,21 @@ export function useLink({ const requiresWarning = Boolean( !disableMismatchWarning && - displayText && - isExternal && - linkRequiresWarning(href, displayText), + displayText && + isExternal && + linkRequiresWarning(href, displayText), ) if (IS_WEB) { e.preventDefault() } + const chatInviteCode = getChatInviteCodeFromUrl(href) + if (chatInviteCode) { + groupChatJoinIntent(chatInviteCode, href) + return + } + if (requiresWarning) { linkWarningDialogControl.open({ displayText, @@ -214,15 +248,16 @@ export function useLink({ overridePresentation, shouldProxy, linkWarningDialogControl, + groupChatJoinIntent, ], ) const handleLongPress = useCallback(() => { const requiresWarning = Boolean( !disableMismatchWarning && - displayText && - isExternal && - linkRequiresWarning(href, displayText), + displayText && + isExternal && + linkRequiresWarning(href, displayText), ) if (requiresWarning) { @@ -251,11 +286,19 @@ export function useLink({ [outerOnLongPress, handleLongPress, shareOnLongPress], ) + // Opens the link through the normal external flow (consent dialog, in-app + // browser, or system browser per preference). Used by the peek menu when the + // in-app browser is off, so committing the peek behaves like a plain tap. + const openExternally = useCallback(() => { + void openLink(href, overridePresentation, shouldProxy) + }, [openLink, href, overridePresentation, shouldProxy]) + return { isExternal, href, onPress, onLongPress, + openExternally, } } @@ -281,9 +324,10 @@ export function Link({ download, shouldProxy, overridePresentation, + peek, ...rest }: LinkProps) { - const {href, isExternal, onPress, onLongPress} = useLink({ + const {href, isExternal, onPress, onLongPress, openExternally} = useLink({ to, displayText: typeof children === 'string' ? children : '', action, @@ -293,7 +337,10 @@ export function Link({ overridePresentation, }) - return ( + // Peek is iOS-only and only makes sense for external web links. + const peekEnabled = Boolean(peek && IS_IOS && isExternal) + + const button = ( ) } @@ -191,7 +190,7 @@ export function Action({ shouldCloseOnPress?: boolean testID?: string }) { - const {_} = useLingui() + const {t: l} = useLingui() const {close} = Dialog.useDialogContext() const handleOnPress = useCallback( (e: GestureResponderEvent) => { @@ -209,10 +208,10 @@ export function Action({ color={color} disabled={disabled} size="large" - label={cta || _(msg`Confirm`)} + label={cta || l`Confirm`} onPress={handleOnPress} testID={testID}> - {cta || _(msg`Confirm`)} + {cta || l`Confirm`} {icon && } ) @@ -225,6 +224,7 @@ export function Basic({ cancelButtonCta, confirmButtonCta, onConfirm, + onClose, confirmButtonColor, showCancel = true, }: React.PropsWithChildren<{ @@ -241,11 +241,12 @@ export function Basic({ * should NOT close the dialog as a side effect of this method. */ onConfirm: (e: GestureResponderEvent) => void + onClose?: () => void confirmButtonColor?: ButtonColor showCancel?: boolean }>) { return ( - + {title} {description && {description}} diff --git a/src/components/RichText.tsx b/src/components/RichText.tsx index 322fb9ebd9..25af851723 100644 --- a/src/components/RichText.tsx +++ b/src/components/RichText.tsx @@ -66,7 +66,7 @@ export function RichText({ } }, [value]) - const plainStyles = [a.leading_snug, style] + const plainStyles = style const interactiveStyles = [plainStyles, interactiveStyle] const {text, facets} = richText diff --git a/src/components/RichTextTag.tsx b/src/components/RichTextTag.tsx index 3325671b6a..acb25a74d7 100644 --- a/src/components/RichTextTag.tsx +++ b/src/components/RichTextTag.tsx @@ -62,7 +62,7 @@ export function RichTextTag({ optimisticUpsert?.find( m => m.value === tag && m.targets.includes('tag'), )) && - !optimisticRemove?.find(m => m?.value === tag), + !optimisticRemove?.find(m => m?.value === tag), ) /* @@ -74,7 +74,7 @@ export function RichTextTag({ return word.value === tag }) || [] ) - }, [tag, preferences?.moderationPrefs?.mutedWords]) + }, [tag, preferences?.moderationPrefs.mutedWords]) return ( diff --git a/src/components/SendErrorReportDialog.tsx b/src/components/SendErrorReportDialog.tsx new file mode 100644 index 0000000000..a677f54e03 --- /dev/null +++ b/src/components/SendErrorReportDialog.tsx @@ -0,0 +1,108 @@ +import {useState} from 'react' +import {View} from 'react-native' +import {useLingui} from '@lingui/react/macro' +import {useMutation} from '@tanstack/react-query' + +import {logger} from '#/logger' +import {sendErrorReport} from '#/logger/reporting/sendErrorReport' +import {useSession} from '#/state/session' +import {atoms as a, web} from '#/alf' +import {Button, ButtonText} from '#/components/Button' +import * as Dialog from '#/components/Dialog' +import * as TextField from '#/components/forms/TextField' +import * as Toast from '#/components/Toast' +import {Text} from '#/components/Typography' + +export function SendErrorReportDialog({ + control, +}: { + control: Dialog.DialogControlProps +}) { + return ( + + + + + + ) +} + +function SendErrorReportDialogInner() { + const {t: l} = useLingui() + const control = Dialog.useDialogContext() + const {currentAccount} = useSession() + const [title, setTitle] = useState('') + const [description, setDescription] = useState('') + + const {mutate: onSubmit, isPending} = useMutation({ + mutationFn: async () => { + sendErrorReport({ + title, + description, + handle: currentAccount?.handle ?? '', + }) + }, + onSuccess: () => { + control.close(() => { + Toast.show(l`Report sent`) + }) + }, + onError: error => { + logger.error('Error sending user report', {safeMessage: error}) + Toast.show(l`Failed to send report`, {type: 'error'}) + }, + }) + + const canSubmit = title.trim().length > 0 && !isPending + + return ( + + + + {l`Send error report`} + + + + {l`Title`} + + setTitle(value.slice(0, 100))} + /> + + + + {l`Description`} + setDescription(value.slice(0, 1000))} + /> + + + + + + + + + ) +} diff --git a/src/components/StarterPack/QrCodeDialog.tsx b/src/components/StarterPack/QrCodeDialog.tsx index 4452c036a5..e239eee9a4 100644 --- a/src/components/StarterPack/QrCodeDialog.tsx +++ b/src/components/StarterPack/QrCodeDialog.tsx @@ -1,8 +1,7 @@ import {Suspense, useRef, useState} from 'react' import {View} from 'react-native' import type ViewShot from 'react-native-view-shot' -import {requestMediaLibraryPermissionsAsync} from 'expo-image-picker' -import {createAssetAsync} from 'expo-media-library' +import {requestPermissionsAsync, saveToLibraryAsync} from 'expo-media-library' import * as Sharing from 'expo-sharing' import {type AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' import {msg} from '@lingui/core/macro' @@ -60,7 +59,9 @@ export function QrCodeDialog({ const onSavePress = async () => { ref.current?.capture?.().then(async (uri: string) => { if (IS_NATIVE) { - const res = await requestMediaLibraryPermissionsAsync() + // Write-only permission - saving the QR image does not require read + // access to the user's photo library. + const res = await requestPermissionsAsync(true) if (!res.granted) { Toast.show( @@ -73,7 +74,9 @@ export function QrCodeDialog({ // Incase of a FS failure, don't crash the app try { - await createAssetAsync(`file://${uri}`) + // saveToLibraryAsync writes without reading the asset back, so it + // works with the add-only permission on iOS (APP-2374) + await saveToLibraryAsync(`file://${uri}`) } catch (e: unknown) { Toast.show(_(msg`An error occurred while saving the QR code!`), { type: 'error', diff --git a/src/components/Toast/Toast.tsx b/src/components/Toast/Toast.tsx index ac5bc4889a..3e11cf051e 100644 --- a/src/components/Toast/Toast.tsx +++ b/src/components/Toast/Toast.tsx @@ -8,8 +8,10 @@ import { type UninheritableButtonProps, } from '#/components/Button' import {CircleCheck_Stroke2_Corner0_Rounded as CircleCheck} from '#/components/icons/CircleCheck' -import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' -import {CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon} from '#/components/icons/CircleInfo' +import { + CircleInfo_Stroke2_Corner0_Rounded as CircleInfo, + CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon, +} from '#/components/icons/CircleInfo' import {type Props as SVGIconProps} from '#/components/icons/common' import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' import {dismiss} from '#/components/Toast/sonner' diff --git a/src/components/Typography.tsx b/src/components/Typography.tsx index 04b5e5499d..b105ffbb8c 100644 --- a/src/components/Typography.tsx +++ b/src/components/Typography.tsx @@ -23,6 +23,7 @@ export function Text({ title, dataSet, numberOfLines, + allowFontScaling = true, ...rest }: TextProps) { const {fonts, flags} = useAlf() @@ -31,11 +32,12 @@ export function Text({ [ a.text_sm, t.atoms.text, + a.leading_snug, web(numberOfLines === 1 && numberOfLinesClippingFix), style, ], { - fontScale: fonts.scaleMultiplier, + fontScale: allowFontScaling ? fonts.scaleMultiplier : 1, fontFamily: fonts.family, flags, }, @@ -56,6 +58,7 @@ export function Text({ numberOfLines, style: s, dataSet: Object.assign({tooltip: title}, dataSet || {}), + allowFontScaling, ...rest, } diff --git a/src/components/ageAssurance/AgeAssuranceAccountCard.tsx b/src/components/ageAssurance/AgeAssuranceAccountCard.tsx index 09dc1e976e..3f4f1f98f6 100644 --- a/src/components/ageAssurance/AgeAssuranceAccountCard.tsx +++ b/src/components/ageAssurance/AgeAssuranceAccountCard.tsx @@ -1,9 +1,8 @@ import {View} from 'react-native' -import {msg} from '@lingui/core/macro' -import {useLingui} from '@lingui/react' -import {Trans} from '@lingui/react/macro' +import {Trans, useLingui} from '@lingui/react/macro' import {dateDiff, useGetTimeAgo} from '#/lib/hooks/useTimeAgo' +import {regionName} from '#/locale/helpers' import {atoms as a, useBreakpoints, useTheme, type ViewStyleProp} from '#/alf' import {Admonition} from '#/components/Admonition' import {AgeAssuranceAppealDialog} from '#/components/ageAssurance/AgeAssuranceAppealDialog' @@ -25,7 +24,38 @@ import {useAgeAssurance} from '#/ageAssurance' import {useComputeAgeAssuranceRegionAccess} from '#/ageAssurance/useComputeAgeAssuranceRegionAccess' import {useAnalytics} from '#/analytics' import {IS_NATIVE} from '#/env' -import {useDeviceGeolocationApi} from '#/geolocation' +import { + type Geolocation, + useDeviceGeolocationApi, + useGeolocation, +} from '#/geolocation' +import {USRegionNameToRegionCode} from '#/geolocation/util' +import {device, useStorage} from '#/storage' + +const USRegionCodeToRegionName: {[regionCode: string]: string} = + Object.fromEntries( + Object.entries(USRegionNameToRegionCode).map(([name, code]) => [ + code, + name, + ]), + ) + +function formatRegion( + geolocation: Geolocation, + appLang: string, +): string | undefined { + const {countryCode, regionCode} = geolocation + if (!countryCode) return undefined + const country = regionName(countryCode, appLang) + // If `regionName` couldn't resolve a real name and fell through to the raw + // code, we'd rather show nothing than a bare ISO code in the prose. + if (country === countryCode) return undefined + if (regionCode && countryCode === 'US') { + const state = USRegionCodeToRegionName[regionCode] + if (state) return `${state}, ${country}` + } + return country +} export function AgeAssuranceAccountCard({style}: ViewStyleProp & {}) { const aa = useAgeAssurance() @@ -42,15 +72,12 @@ export function AgeAssuranceAccountCard({style}: ViewStyleProp & {}) { function Inner({style}: ViewStyleProp & {}) { const t = useTheme() - const {_, i18n} = useLingui() + const {t: l, i18n} = useLingui() const ax = useAnalytics() const control = useDialogControl() const appealControl = Dialog.useDialogControl() - const locationControl = Dialog.useDialogControl() const getTimeAgo = useGetTimeAgo() const {gtPhone} = useBreakpoints() - const {setDeviceGeolocation} = useDeviceGeolocationApi() - const computeAgeAssuranceRegionAccess = useComputeAgeAssuranceRegionAccess() const copy = useAgeAssuranceCopy() const aa = useAgeAssurance() @@ -97,7 +124,7 @@ function Inner({style}: ViewStyleProp & {}) { if one is available in your region. If you have questions or concerns,{' '} { appealControl.open() })}> @@ -107,47 +134,7 @@ function Inner({style}: ViewStyleProp & {}) { )} - {IS_NATIVE && ( - <> - - - Is your location not accurate?{' '} - { - locationControl.open() - })}> - Tap here to confirm your location. - {' '} - - - - { - const access = computeAgeAssuranceRegionAccess( - props.geolocation, - ) - if (access !== aa.Access.Full) { - props.disableDialogAction() - props.setDialogError( - _( - msg`We're sorry, but based on your device's location, you are currently located in a region that requires age assurance.`, - ), - ) - } else { - props.closeDialog(() => { - // set this after close! - setDeviceGeolocation(props.geolocation) - Toast.show(_(msg`Thanks! You're all set.`), { - type: 'success', - }) - }) - } - }} - /> - - )} + {isBlocked ? ( @@ -156,7 +143,7 @@ function Inner({style}: ViewStyleProp & {}) { You are currently unable to access Bluesky's Age Assurance flow. Please{' '} { appealControl.open() ax.metric('ageAssurance:appealDialogOpen', {}) @@ -182,7 +169,7 @@ function Inner({style}: ViewStyleProp & {}) { : [a.gap_md], ]}> ) } @@ -273,7 +282,7 @@ export function Verify({config, showScreen}: ScreenProps) { If you need to update your email,{' '} { showScreen({id: ScreenID.Update}) })}> @@ -288,12 +297,11 @@ export function Verify({config, showScreen}: ScreenProps) { )} - {state.step === 'email' && state.mutationStatus !== 'success' ? ( <> {state.error && {state.error}} ) : null} - {state.step === 'email' && ( <> @@ -318,7 +325,7 @@ export function Verify({config, showScreen}: ScreenProps) { Have a code?{' '} { dispatch({ type: 'setStep', @@ -331,7 +338,6 @@ export function Verify({config, showScreen}: ScreenProps) { )} - {state.step === 'token' ? ( <> ) { {state.error && {state.error}} - )} - - - - { - setSearch(text) - listRef.current?.scrollToOffset({offset: 0, animated: false}) - }} - returnKeyType="search" - clearButtonMode="while-editing" - inputRef={textInputRef} - maxLength={50} - onKeyPress={({nativeEvent}) => { - if (nativeEvent.key === 'Escape') { - control.close() - } - }} - /> - - - ) - }, [gtMobile, t.atoms.bg, l, control, klipyEnabled]) - - return ( - <> - {gtMobile && } - - {listHeader} - {!hasData && ( - - )} - - } - stickyHeaderIndices={[0]} - onEndReached={onEndReached} - onEndReachedThreshold={4} - keyExtractor={(item: Gif) => item.id} - keyboardDismissMode="on-drag" - ListFooterComponent={ - hasData ? ( - - ) : null - } - /> - - ) -} - -function DialogError({details}: {details?: string}) { - const {t: l} = useLingui() - const control = Dialog.useDialogContext() - - return ( - - - - - - ) -} - -export function GifPreview({ - gif, - onSelectGif, -}: { - gif: Gif - onSelectGif: (gif: Gif) => void -}) { - const ax = useAnalytics() - const {gtTablet} = useBreakpoints() - const {t: l} = useLingui() - const t = useTheme() - - const onPress = useCallback(() => { - ax.metric('composer:gif:select', {}) - onSelectGif(gif) - }, [ax, onSelectGif, gif]) - - return ( - - ) -} diff --git a/src/components/dialogs/NotificationSettingsDialog.tsx b/src/components/dialogs/NotificationSettingsDialog.tsx new file mode 100644 index 0000000000..c62f6a7d98 --- /dev/null +++ b/src/components/dialogs/NotificationSettingsDialog.tsx @@ -0,0 +1,105 @@ +import {View} from 'react-native' +import {type AppBskyNotificationDefs} from '@atproto/api' +import {Trans, useLingui} from '@lingui/react/macro' + +import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings' +import * as SettingsList from '#/screens/Settings/components/SettingsList' +import {PreferenceControls} from '#/screens/Settings/NotificationSettings/components/PreferenceControls' +import {atoms as a, useTheme, web} from '#/alf' +import {Admonition} from '#/components/Admonition' +import {Button, ButtonText} from '#/components/Button' +import * as Dialog from '#/components/Dialog' +import {type Props as SVGIconProps} from '#/components/icons/common' +import {Text} from '#/components/Typography' +import {IS_NATIVE} from '#/env' + +type NotificationSettingsDialogProps = { + control: Dialog.DialogControlProps + name: Exclude + syncOthers?: Exclude[] + icon: React.ComponentType + titleText: React.ReactNode + subtitleText: React.ReactNode + allowDisableInApp?: boolean +} + +export function NotificationSettingsDialog({ + control, + name, + syncOthers, + titleText, + subtitleText, + allowDisableInApp = true, +}: NotificationSettingsDialogProps) { + return ( + + + + ) +} + +function NotificationSettingsDialogInner({ + control, + name, + syncOthers, + titleText, + subtitleText, + allowDisableInApp, +}: Omit) { + const t = useTheme() + const {t: l} = useLingui() + const {data: preferences, isError} = useNotificationSettingsQuery() + + return ( + <> + + + + + {titleText} + + {subtitleText} + + + {isError ? ( + + + Failed to load notification settings. + + + ) : ( + + )} + + + {IS_NATIVE && ( + + )} + + + ) +} diff --git a/src/components/dialogs/SearchablePeopleList.tsx b/src/components/dialogs/SearchablePeopleList.tsx index e5675a8339..cf106684a4 100644 --- a/src/components/dialogs/SearchablePeopleList.tsx +++ b/src/components/dialogs/SearchablePeopleList.tsx @@ -114,6 +114,7 @@ export function SearchablePeopleList({ const {data: convos} = useListConvosQuery({ enabled: showRecentConvos, status: 'accepted', + lockStatus: 'unlocked', }) const items = useMemo(() => { @@ -479,14 +480,15 @@ function ExistingChatCard({ const {t: l} = useLingui() const enabled = convo.kind === 'group' ? convo.details.lockStatus === 'unlocked' : true - const moderation = moderateProfile(convo.primaryMember, moderationOpts) const name = convo.kind === 'group' ? convo.details.name : createSanitizedDisplayName( convo.primaryMember, true, - moderation.ui('displayName'), + moderateProfile(convo.primaryMember, moderationOpts).ui( + 'displayName', + ), ) const handleOnPress = useCallback(() => { @@ -551,7 +553,7 @@ function ExistingChatCard({ style={[a.leading_snug, t.atoms.text_contrast_medium]} numberOfLines={2}> @@ -661,7 +663,6 @@ function SearchInput({ /> { @@ -69,13 +67,13 @@ function DialogInner({ ) }, - [onChange, memberships, list, moderationOpts], + [onChange, listMembers, list, moderationOpts], ) return ( @@ -86,16 +84,30 @@ function DialogInner({ ) } +/** + * Returns undefined for pending, false for not a member, and string for a member (the URI of the membership record) + */ +function getMembership( + listMembers: AppBskyGraphDefs.ListItemView[] | undefined, + actorDid: string, +): string | false | undefined { + if (!listMembers) { + return undefined + } + const member = listMembers.find(item => item.subject.did === actorDid) + return member ? member.uri : false +} + function UserResult({ profile, list, - memberships, + listMembers, onChange, moderationOpts, }: { profile: bsky.profile.AnyProfileView list: AppBskyGraphDefs.ListView - memberships: ListMembersip[] | undefined + listMembers: AppBskyGraphDefs.ListItemView[] | undefined onChange?: ( type: 'add' | 'remove', profile: bsky.profile.AnyProfileView, @@ -104,8 +116,8 @@ function UserResult({ }) { const {_} = useLingui() const membership = useMemo( - () => getMembership(memberships, list.uri, profile.did), - [memberships, list.uri, profile.did], + () => getMembership(listMembers, profile.did), + [listMembers, profile.did], ) const {mutate: listMembershipAdd, isPending: isAddingPending} = useListMembershipAddMutation({ diff --git a/src/components/dialogs/lists/UserAddRemoveListsDialog.tsx b/src/components/dialogs/lists/UserAddRemoveListsDialog.tsx new file mode 100644 index 0000000000..de8f453d5a --- /dev/null +++ b/src/components/dialogs/lists/UserAddRemoveListsDialog.tsx @@ -0,0 +1,331 @@ +import {View} from 'react-native' +import {Trans, useLingui} from '@lingui/react/macro' +import {useQueryClient} from '@tanstack/react-query' + +import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' +import {sanitizeDisplayName} from '#/lib/strings/display-names' +import {isNetworkError} from '#/lib/strings/errors' +import {sanitizeHandle} from '#/lib/strings/handles' +import {logger} from '#/logger' +import { + useListMembershipAddMutation, + useListMembershipRemoveMutation, +} from '#/state/queries/list-memberships' +import { + type ListWithMembership, + removeListMembershipOptimistically, + updateListMembershipOptimistically, + useListsWithMembershipQuery, +} from '#/state/queries/lists-with-membership' +import {useSession} from '#/state/session' +import {UserAvatar} from '#/view/com/util/UserAvatar' +import {atoms as a, platform, useBreakpoints, useTheme, web} from '#/alf' +import {Button, ButtonIcon, ButtonText} from '#/components/Button' +import * as Dialog from '#/components/Dialog' +import {BulletList_Stroke2_Corner0_Rounded as ListIcon} from '#/components/icons/BulletList' +import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' +import {Loader} from '#/components/Loader' +import * as Toast from '#/components/Toast' +import {Text} from '#/components/Typography' +import type * as bsky from '#/types/bsky' + +export type UserAddRemoveListsDialogProps = { + control: Dialog.DialogControlProps + profile: bsky.profile.AnyProfileView | undefined + onAdd?: (listUri: string) => void + onRemove?: (listUri: string) => void +} + +export function UserAddRemoveListsDialog({ + control, + profile, + onAdd, + onRemove, +}: UserAddRemoveListsDialogProps) { + return ( + + + + + ) +} + +function Empty() { + const t = useTheme() + + return ( + + + + + You have no lists. + + + + ) +} + +function ListsContent({ + profile, + onAdd, + onRemove, +}: { + profile: bsky.profile.AnyProfileView | undefined + onAdd?: (listUri: string) => void + onRemove?: (listUri: string) => void +}) { + const t = useTheme() + const control = Dialog.useDialogContext() + const {t: l} = useLingui() + const {gtMobile} = useBreakpoints() + + const { + data, + isError, + isLoading, + hasNextPage, + isFetchingNextPage, + fetchNextPage, + } = useListsWithMembershipQuery({actor: profile?.did}) + + const listItems = data?.pages.flatMap(page => page.listsWithMembership) || [] + + const onEndReached = async () => { + if (isFetchingNextPage || !hasNextPage || isError) return + try { + await fetchNextPage() + } catch (err) { + // Error handling is optional since this is just pagination + } + } + + const renderItem = ({item}: {item: ListWithMembership}) => + profile ? ( + + ) : null + + const onClose = () => { + control.close() + } + + const listHeader = ( + + + {profile ? ( + + Update {createSanitizedDisplayName(profile, true)} in Lists + + ) : ( + Update in Lists + )} + + + + ) + + return ( + ( + + + + ) + : renderItem + } + keyExtractor={ + isLoading + ? () => 'lists_dialog_loader' + : (item: ListWithMembership) => item.list.uri + } + onEndReached={() => void onEndReached()} + onEndReachedThreshold={0.1} + stickyHeaderIndices={web([0])} + ListHeaderComponent={listHeader} + ListFooterComponent={ + isFetchingNextPage ? ( + + + + ) : null + } + ListEmptyComponent={!isLoading && data ? : null} + webInnerContentContainerStyle={[a.py_0]} + style={platform({ + web: [a.px_2xl, a.pb_md], + native: [a.px_2xl, a.pt_lg], + })} + /> + ) +} + +function ListItem({ + listWithMembership, + profile, + onAdd, + onRemove, +}: { + listWithMembership: ListWithMembership + profile: bsky.profile.AnyProfileView + onAdd?: (listUri: string) => void + onRemove?: (listUri: string) => void +}) { + const {t: l} = useLingui() + const t = useTheme() + const queryClient = useQueryClient() + const {currentAccount} = useSession() + + const list = listWithMembership.list + const listItem = listWithMembership.listItem + const isMember = !!listItem + + const {mutate: addMembership, isPending: isPendingAdd} = + useListMembershipAddMutation({ + subject: profile, + onSuccess: data => { + Toast.show(l`Added to list`) + onAdd?.(list.uri) + updateListMembershipOptimistically({ + queryClient, + actor: profile.did, + listUri: list.uri, + membershipUri: data.uri, + subject: { + did: profile.did, + handle: profile.handle, + displayName: profile.displayName, + }, + }) + }, + onError: err => { + if (!isNetworkError(err)) { + logger.error('Failed to add to list', {safeMessage: err}) + } + Toast.show(l`Failed to add to list`, {type: 'error'}) + }, + }) + + const {mutate: removeMembership, isPending: isPendingRemove} = + useListMembershipRemoveMutation({ + onSuccess: () => { + Toast.show(l`Removed from list`) + onRemove?.(list.uri) + removeListMembershipOptimistically({ + queryClient, + actor: profile.did, + listUri: list.uri, + }) + }, + onError: err => { + if (!isNetworkError(err)) { + logger.error('Failed to remove from list', {safeMessage: err}) + } + Toast.show(l`Failed to remove from list`, {type: 'error'}) + }, + }) + + const isPending = isPendingAdd || isPendingRemove + + const handleToggleMembership = () => { + if (isPending) return + + if (!isMember) { + addMembership({ + listUri: list.uri, + actorDid: profile.did, + }) + } else { + if (!listItem?.uri) { + logger.error('Cannot remove: missing membership URI') + return + } + removeMembership({ + listUri: list.uri, + actorDid: profile.did, + membershipUri: listItem.uri, + }) + } + } + + return ( + + + + + {sanitizeDisplayName(list.name)} + + + {list.purpose === 'app.bsky.graph.defs#curatelist' && + (list.creator.did === currentAccount?.did ? ( + User list by you + ) : ( + + User list by {sanitizeHandle(list.creator.handle, '@')} + + ))} + {list.purpose === 'app.bsky.graph.defs#modlist' && + (list.creator.did === currentAccount?.did ? ( + Moderation list by you + ) : ( + + Moderation list by {sanitizeHandle(list.creator.handle, '@')} + + ))} + + + + + ) +} diff --git a/src/components/dialogs/nuxs/ActivitySubscriptions.tsx b/src/components/dialogs/nuxs/ActivitySubscriptions.tsx index ed4306459c..318b130414 100644 --- a/src/components/dialogs/nuxs/ActivitySubscriptions.tsx +++ b/src/components/dialogs/nuxs/ActivitySubscriptions.tsx @@ -113,6 +113,7 @@ export function ActivitySubscriptionsNUX() { alt={_( msg`A screenshot of a profile page with a bell icon next to the follow button, indicating the new activity notifications feature.`, )} + useAppleWebpCodec /> diff --git a/src/components/dialogs/nuxs/BookmarksAnnouncement.tsx b/src/components/dialogs/nuxs/BookmarksAnnouncement.tsx index f7e4bf04c6..153130f8ec 100644 --- a/src/components/dialogs/nuxs/BookmarksAnnouncement.tsx +++ b/src/components/dialogs/nuxs/BookmarksAnnouncement.tsx @@ -124,6 +124,7 @@ export function BookmarksAnnouncement() { 'Contains a post that originally appeared in English. Consider translating the post text if it makes sense in your language, and noting that the post was translated from English.', }), )} + useAppleWebpCodec /> diff --git a/src/components/dialogs/nuxs/DraftsAnnouncement.tsx b/src/components/dialogs/nuxs/DraftsAnnouncement.tsx index 79488fe4d9..37b93bda70 100644 --- a/src/components/dialogs/nuxs/DraftsAnnouncement.tsx +++ b/src/components/dialogs/nuxs/DraftsAnnouncement.tsx @@ -101,6 +101,7 @@ export function DraftsAnnouncement() { 'Contains a post that originally appeared in English. Consider translating the post text if it makes sense in your language, and noting that the post was translated from English.', }), )} + useAppleWebpCodec /> diff --git a/src/components/dialogs/nuxs/FindContactsAnnouncement.tsx b/src/components/dialogs/nuxs/FindContactsAnnouncement.tsx index 8fd7d2d2c5..70f29ef157 100644 --- a/src/components/dialogs/nuxs/FindContactsAnnouncement.tsx +++ b/src/components/dialogs/nuxs/FindContactsAnnouncement.tsx @@ -78,6 +78,7 @@ export function FindContactsAnnouncement() { alt={_( msg`An illustration depicting user avatars flowing from a contact book into the Bluesky app`, )} + useAppleWebpCodec /> diff --git a/src/components/dialogs/nuxs/GroupChatsAnnouncement.tsx b/src/components/dialogs/nuxs/GroupChatsAnnouncement.tsx new file mode 100644 index 0000000000..0535b3478f --- /dev/null +++ b/src/components/dialogs/nuxs/GroupChatsAnnouncement.tsx @@ -0,0 +1,241 @@ +import {useCallback, useState} from 'react' +import {View} from 'react-native' +import {useSafeAreaInsets} from 'react-native-safe-area-context' +import {Image} from 'expo-image' +import {type ThemeName} from '@bsky.app/alf' +import {Trans, useLingui} from '@lingui/react/macro' +import {useNavigation} from '@react-navigation/native' + +import {type NavigationProp} from '#/lib/routes/types' +import { + atoms as a, + native, + platform, + type TextStyleProp, + useTheme, + web, +} from '#/alf' +import {Button, ButtonText} from '#/components/Button' +import * as Dialog from '#/components/Dialog' +import {useNuxDialogContext} from '#/components/dialogs/nuxs' +import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink' +import {type Props as SVGIconProps} from '#/components/icons/common' +import {Group3_Stroke2_Corner0_Rounded as GroupIcon} from '#/components/icons/Group' +import {Shield_Stroke2_Corner0_Rounded as ShieldIcon} from '#/components/icons/Shield' +import {Sparkle_Stroke2_Corner0_Rounded as SparkleIcon} from '#/components/icons/Sparkle' +import {Text} from '#/components/Typography' +import {IS_E2E, IS_WEB} from '#/env' +import {createIsEnabledCheck, isExistingUserAsOf} from './utils' + +// Gate: only show to existing users (created before 2026-06-11), not E2E +export const enabled = createIsEnabledCheck(props => { + return ( + !IS_E2E && + isExistingUserAsOf( + '2026-06-11T00:00:00.000Z', + props.currentProfile.createdAt, + ) + ) +}) + +function getHero(theme: ThemeName) { + switch (theme) { + case 'light': + return require('../../../../assets/images/groupchats_announcement_light.webp') + case 'dark': + return require('../../../../assets/images/groupchats_announcement_dark.webp') + case 'dim': + return require('../../../../assets/images/groupchats_announcement_dim.webp') + } +} + +export function GroupChatsAnnouncement() { + const t = useTheme() + const {t: l} = useLingui() + const navigation = useNavigation() + const nuxDialogs = useNuxDialogContext() + const control = Dialog.useDialogControl() + const {bottom} = useSafeAreaInsets() + + // Measure the footer so the scrollable content can pad itself out from + // underneath it (the footer is absolutely positioned). + const [footerHeight, setFooterHeight] = useState( + platform({ + native: 124 + bottom, + web: 128, + default: 0, + }), + ) + + Dialog.useAutoOpen(control) + + const onClose = useCallback(() => { + nuxDialogs.dismissActiveNux() + }, [nuxDialogs]) + + const onPressStartGroupChat = useCallback(() => { + control.close(() => { + if (IS_WEB) { + navigation.navigate('Messages', {pushToNewGroupChat: true}) + } else { + // On native, Messages is nested inside MessagesTab. + // @ts-expect-error nested navigators aren't typed -sfn + navigation.navigate('MessagesTab', { + screen: 'Messages', + params: {pushToNewGroupChat: true}, + }) + } + }) + }, [control, navigation]) + + return ( + + + setFooterHeight(evt.nativeEvent.layout.height)} + border={false}> + + + + + + }> + + {l({ + + + + + + + New + + + + Group Chats + + + Take the conversation private. + + + + + Add people with a link} + descriptionText={ + Post it to Bluesky or share anywhere. + } + /> + Up to 50 people} + descriptionText={ + Bring up to 50 friends together in one chat. + } + /> + You’re in control} + descriptionText={ + + Mute, leave, or remove people anytime. It’s your chat. + + } + /> + + + + + ) +} + +function Feature({ + icon: Icon, + titleText, + descriptionText, + style, +}: { + icon: React.ComponentType + titleText: React.ReactNode + descriptionText: React.ReactNode +} & TextStyleProp) { + const t = useTheme() + return ( + + + + {titleText} + + {descriptionText} + + + + ) +} diff --git a/src/components/dialogs/nuxs/InitialVerificationAnnouncement.tsx b/src/components/dialogs/nuxs/InitialVerificationAnnouncement.tsx index 85bc29a48f..cbe2555aac 100644 --- a/src/components/dialogs/nuxs/InitialVerificationAnnouncement.tsx +++ b/src/components/dialogs/nuxs/InitialVerificationAnnouncement.tsx @@ -85,6 +85,7 @@ export function InitialVerificationAnnouncement() { alt={_( msg`An illustration showing that Bluesky selects trusted verifiers, and trusted verifiers in turn verify individual user accounts.`, )} + useAppleWebpCodec /> @@ -119,6 +120,7 @@ export function InitialVerificationAnnouncement() { alt={_( msg`An mockup of a iPhone showing the Bluesky app open to the profile of a verified user with a blue checkmark next to their display name.`, )} + useAppleWebpCodec /> diff --git a/src/components/dialogs/nuxs/InviteFriendsAnnouncement.tsx b/src/components/dialogs/nuxs/InviteFriendsAnnouncement.tsx new file mode 100644 index 0000000000..48e4c05965 --- /dev/null +++ b/src/components/dialogs/nuxs/InviteFriendsAnnouncement.tsx @@ -0,0 +1,193 @@ +import {useCallback} from 'react' +import {View} from 'react-native' +import {Image} from 'expo-image' +import {Trans, useLingui} from '@lingui/react/macro' + +import {useCallOnce} from '#/lib/once' +import {Nux} from '#/state/queries/nuxs' +import {atoms as a, useTheme, web} from '#/alf' +import {themes} from '#/alf/themes' +import {Button, ButtonText} from '#/components/Button' +import * as Dialog from '#/components/Dialog' +import {useNuxDialogContext} from '#/components/dialogs/nuxs' +import {Sparkle_Stroke2_Corner0_Rounded as SparkleIcon} from '#/components/icons/Sparkle' +import {Text} from '#/components/Typography' +import {useAnalytics} from '#/analytics' +import {IS_E2E, IS_NATIVE, IS_WEB} from '#/env' +import {InviteFriendsDialog} from '#/features/inviteFriends' +import {createIsEnabledCheck} from './utils' + +export const enabled = createIsEnabledCheck(() => { + // Native-only feature (matches the rest of the invite-friends scope). + // Skipped in E2E to keep tests deterministic. + return !IS_E2E && IS_NATIVE +}) + +export function InviteFriendsAnnouncement() { + const {activeNux} = useNuxDialogContext() + /* + * The invite dialog's control lives here (outside the activeNux + * conditional) so the dialog survives the announcement being dismissed + * during the "Try it" handoff - the native bottom sheet cannot hand off to + * a second sheet that unmounts along with the first. + */ + const inviteFriendsControl = Dialog.useDialogControl() + + return ( + <> + {activeNux === Nux.InviteFriendsAnnouncement && ( + + )} + + + ) +} + +function Announcement({ + inviteFriendsControl, +}: { + inviteFriendsControl: Dialog.DialogControlProps +}) { + const t = useTheme() + const {t: l} = useLingui() + const ax = useAnalytics() + const nuxDialogs = useNuxDialogContext() + const control = Dialog.useDialogControl() + + Dialog.useAutoOpen(control) + + useCallOnce(() => { + ax.metric('invite:nux:presented', {}) + })() + + const onClose = useCallback(() => { + nuxDialogs.dismissActiveNux() + }, [nuxDialogs]) + + const onPressTryIt = useCallback(() => { + ax.metric('invite:nux:tryItPressed', {}) + // Close this announcement (which dismisses the NUX and unmounts this + // component), then open the invite dialog. Its control lives in the + // parent, which stays mounted, so the dialog survives the dismissal. + // Defer the open to the next frame so the announcement's teardown + // completes first. + control.close(() => { + requestAnimationFrame(() => { + ax.metric('invite:dialog:open', {logContext: 'NuxAnnouncement'}) + inviteFriendsControl.open() + }) + }) + }, [ax, control, inviteFriendsControl]) + + return ( + <> + + + + + + {/* The image is the full header background; the tag sits on top of it. */} + {l`An + + {/* Pinned to the light palette: the header illustration has a + fixed light background, so the tag must stay dark in any + theme to remain legible. */} + + + + New Feature + + + + + + + + Invite your friends + + + + You can now invite friends with a link or QR code. Share the + link anywhere, or let them scan the code to join you on + Bluesky. + + + + + {!IS_WEB && ( + + + + + )} + + + + + + + ) +} diff --git a/src/components/dialogs/nuxs/LiveNowBetaDialog.tsx b/src/components/dialogs/nuxs/LiveNowBetaDialog.tsx index 49c43a1e41..e389e11175 100644 --- a/src/components/dialogs/nuxs/LiveNowBetaDialog.tsx +++ b/src/components/dialogs/nuxs/LiveNowBetaDialog.tsx @@ -150,6 +150,7 @@ export function LiveNowBetaDialog() { 'Contains a post that originally appeared in English. Consider translating the post text if it makes sense in your language, and noting that the post was translated from English.', }), )} + useAppleWebpCodec /> diff --git a/src/components/dialogs/nuxs/index.tsx b/src/components/dialogs/nuxs/index.tsx index 2d2e2c6a6a..2fa8780d95 100644 --- a/src/components/dialogs/nuxs/index.tsx +++ b/src/components/dialogs/nuxs/index.tsx @@ -19,9 +19,13 @@ import {useProfileQuery} from '#/state/queries/profile' import {type SessionAccount, useSession} from '#/state/session' import {useOnboardingState} from '#/state/shell' import { - DraftsAnnouncement, - enabled as isDraftsAnnouncementEnabled, -} from '#/components/dialogs/nuxs/DraftsAnnouncement' + enabled as isGroupChatsAnnouncementEnabled, + GroupChatsAnnouncement, +} from '#/components/dialogs/nuxs/GroupChatsAnnouncement' +import { + enabled as isInviteFriendsAnnouncementEnabled, + InviteFriendsAnnouncement, +} from '#/components/dialogs/nuxs/InviteFriendsAnnouncement' import {isSnoozed, snooze, unsnooze} from '#/components/dialogs/nuxs/snoozing' import {type EnabledCheckProps} from '#/components/dialogs/nuxs/utils' import {useAnalytics} from '#/analytics' @@ -37,8 +41,12 @@ const queuedNuxs: { enabled?: (props: EnabledCheckProps) => boolean }[] = [ { - id: Nux.DraftsAnnouncement, - enabled: isDraftsAnnouncementEnabled, + id: Nux.GroupChatsAnnouncement, + enabled: isGroupChatsAnnouncementEnabled, + }, + { + id: Nux.InviteFriendsAnnouncement, + enabled: isInviteFriendsAnnouncementEnabled, }, ] @@ -186,7 +194,13 @@ function Inner({ return ( {/*For example, activeNux === Nux.NeueTypography && */} - {activeNux === Nux.DraftsAnnouncement && } + {activeNux === Nux.GroupChatsAnnouncement && } + {/* + Mounted unconditionally: it gates the announcement on `activeNux` + internally, so it can keep the invite-friends dialog mounted across + the announcement's dismissal during the "Try it" handoff. + */} + ) } diff --git a/src/components/dialogs/nuxs/snoozing.ts b/src/components/dialogs/nuxs/snoozing.ts index 91effd0501..94389d312b 100644 --- a/src/components/dialogs/nuxs/snoozing.ts +++ b/src/components/dialogs/nuxs/snoozing.ts @@ -1,6 +1,9 @@ -import {simpleAreDatesEqual} from '#/lib/strings/time' +import {IS_DEV} from '#/env' import {device} from '#/storage' +const PROD_SNOOZE_SECONDS = 3 * 60 * 60 // 3 hours +const SNOOZE_SECONDS = IS_DEV ? 10 : PROD_SNOOZE_SECONDS + export function snooze() { device.set(['lastNuxDialog'], new Date().toISOString()) } @@ -14,9 +17,5 @@ export function isSnoozed() { if (!lastNuxDialog) return false const last = new Date(lastNuxDialog) const now = new Date() - // already snoozed today - if (simpleAreDatesEqual(last, now)) { - return true - } - return false + return now.getTime() - last.getTime() < SNOOZE_SECONDS * 1000 } diff --git a/src/components/dms/ActionsWrapper.tsx b/src/components/dms/ActionsWrapper.tsx index bc0505cda5..ce58dba31c 100644 --- a/src/components/dms/ActionsWrapper.tsx +++ b/src/components/dms/ActionsWrapper.tsx @@ -1,53 +1,51 @@ import {View} from 'react-native' -import {type ChatBskyConvoDefs} from '@atproto/api' +import {type ChatBskyConvoDefs, type ModerationOpts} from '@atproto/api' import {useLingui} from '@lingui/react/macro' -import {atoms as a} from '#/alf' import {MessageContextMenu} from '#/components/dms/MessageContextMenu' +import {useMessageReplies} from '#/components/dms/MessageReplies' +import {SwipeToReply} from '#/components/dms/SwipeToReply' import type * as bsky from '#/types/bsky' export function ActionsWrapper({ message, isFromSelf, senderProfile, + moderationOpts, children, - onTap, }: { message: ChatBskyConvoDefs.MessageView - hasReactions?: boolean isFromSelf: boolean senderProfile?: bsky.profile.AnyProfileView + moderationOpts: ModerationOpts | undefined children: React.ReactNode - onTap?: () => void }) { const {t: l} = useLingui() + const {setReply} = useMessageReplies() return ( - - {trigger => - // will always be true, since this file is platform split - trigger.IS_NATIVE && ( - - trigger.control.open('full')}> - {children} - - - ) - } - + setReply(message)}> + {swipeGesture => ( + + {trigger => + // will always be true, since this file is platform split + trigger.IS_NATIVE && ( + trigger.control.open('full')}> + {children} + + ) + } + + )} + ) } diff --git a/src/components/dms/ActionsWrapper.web.tsx b/src/components/dms/ActionsWrapper.web.tsx index 12ccea03ab..75f816c07d 100644 --- a/src/components/dms/ActionsWrapper.web.tsx +++ b/src/components/dms/ActionsWrapper.web.tsx @@ -1,8 +1,11 @@ import {useCallback, useRef, useState} from 'react' import {Pressable, View} from 'react-native' -import {type ChatBskyConvoDefs} from '@atproto/api' +import {type ChatBskyConvoDefs, type ModerationOpts} from '@atproto/api' +import {plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react/macro' +import {EMOJI_REACTION_LIMIT} from '#/lib/constants' +import {useMaybeProfileShadow} from '#/state/cache/profile-shadow' import {useConvoActive} from '#/state/messages/convo' import {useSession} from '#/state/session' import {atoms as a, useTheme} from '#/alf' @@ -12,28 +15,32 @@ import {EmojiSmile_Stroke2_Corner0_Rounded as EmojiSmileIcon} from '#/components import * as Toast from '#/components/Toast' import type * as bsky from '#/types/bsky' import {EmojiReactionPicker} from './EmojiReactionPicker' -import {hasReachedReactionLimit} from './util' +import {canReact, hasReachedReactionLimit} from './util' export function ActionsWrapper({ message, - hasReactions, isFromSelf, senderProfile, + moderationOpts, children, - onTap, }: { message: ChatBskyConvoDefs.MessageView - hasReactions?: boolean isFromSelf: boolean senderProfile?: bsky.profile.AnyProfileView + moderationOpts: ModerationOpts | undefined children: React.ReactNode - onTap?: () => void }) { const viewRef = useRef(null) const t = useTheme() const {t: l} = useLingui() const convo = useConvoActive() const {currentAccount} = useSession() + const primaryMember = useMaybeProfileShadow(convo.convo.primaryMember) + const reactionsAvailable = canReact({ + convoState: convo, + primaryMember, + moderationOpts, + }) const [showActions, setShowActions] = useState(false) @@ -65,7 +72,18 @@ export function ActionsWrapper({ .removeReaction(message.id, emoji) .catch(() => Toast.show(l`Failed to remove emoji reaction`)) } else { - if (hasReachedReactionLimit(message, currentAccount?.did)) return + if (hasReachedReactionLimit(message, currentAccount?.did)) { + Toast.show( + l`You cannot add more than ${plural(EMOJI_REACTION_LIMIT, { + one: '# emoji reaction', + other: '# emoji reactions', + })}`, + { + type: 'info', + }, + ) + return + } convo.addReaction(message.id, emoji).catch(() => Toast.show(l`Failed to add emoji reaction`, { type: 'error', @@ -93,31 +111,35 @@ export function ActionsWrapper({ isFromSelf ? [a.mr_xs, {marginLeft: 'auto'}, a.flex_row_reverse] : [a.ml_xs, {marginRight: 'auto'}], - hasReactions ? [a.mb_2xl] : undefined, ]}> - - {({props, state, IS_NATIVE, control}) => { - // always false, file is platform split - if (IS_NATIVE) return null - const showMenuTrigger = showActions || control.isOpen ? 1 : 0 - return ( - - - - ) - }} - - + {reactionsAvailable && ( + + {({props, state, IS_NATIVE, control}) => { + // always false, file is platform split + if (IS_NATIVE) return null + const showMenuTrigger = showActions || control.isOpen ? 1 : 0 + return ( + + + + ) + }} + + )} + {({props, state, IS_NATIVE, control}) => { // always false, file is platform split if (IS_NATIVE) return null @@ -140,13 +162,10 @@ export function ActionsWrapper({ }} - {children} - + ) } diff --git a/src/components/dms/AddMembersFlow.tsx b/src/components/dms/AddMembersFlow.tsx index bc3313b07c..bb591fb8c6 100644 --- a/src/components/dms/AddMembersFlow.tsx +++ b/src/components/dms/AddMembersFlow.tsx @@ -11,16 +11,18 @@ import {Trans, useLingui} from '@lingui/react/macro' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' +import {useListConvoMembersQuery} from '#/state/queries/messages/list-convo-members' import {useProfileFollowsQuery} from '#/state/queries/profile-follows' import {useSession} from '#/state/session' import {type ListMethods} from '#/view/com/util/List' import {android, atoms as a, native, useTheme, web} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' -import {canBeMessaged} from '#/components/dms/util' +import {canBeAddedToGroup, type ConvoWithDetails} from '#/components/dms/util' import * as Toggle from '#/components/forms/Toggle' import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeftIcon} from '#/components/icons/Arrow' import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' +import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import {IS_NATIVE, IS_WEB} from '#/env' import type * as bsky from '#/types/bsky' @@ -54,16 +56,17 @@ type PlaceholderItem = { key: string } -type ErrorItem = { - type: 'error' +type LoadingItem = { + type: 'loading' key: string } -type Item = LabelItem | ProfileItem | EmptyItem | PlaceholderItem | ErrorItem +type Item = LabelItem | ProfileItem | EmptyItem | PlaceholderItem | LoadingItem export type State = { groupChatDids: string[] groupChatProfiles: bsky.profile.AnyProfileView[] + searchText: string } export type Action = @@ -77,6 +80,10 @@ export type Action = groupChatDids: string[] groupChatProfiles: bsky.profile.AnyProfileView[] } + | { + type: 'setSearchText' + searchText: string + } function reducer(state: State, action: Action): State { switch (action.type) { @@ -85,6 +92,7 @@ function reducer(state: State, action: Action): State { ...state, groupChatDids: action.groupChatDids, groupChatProfiles: action.groupChatProfiles, + searchText: '', } } case 'removeDids': { @@ -94,15 +102,21 @@ function reducer(state: State, action: Action): State { groupChatProfiles: action.groupChatProfiles, } } + case 'setSearchText': { + return { + ...state, + searchText: action.searchText, + } + } } } export function AddMembersFlow({ - members, + convo, title, onAddMembers, }: { - members: string[] + convo: Extract title: string onAddMembers: ( dids: string[], @@ -112,26 +126,47 @@ export function AddMembersFlow({ const t = useTheme() const {t: l} = useLingui() const moderationOpts = useModerationOpts() + const {currentAccount} = useSession() + const control = Dialog.useDialogContext() + const [headerHeight, setHeaderHeight] = useState(0) const [footerHeight, setFooterHeight] = useState(0) + const listRef = useRef(null) - const {currentAccount} = useSession() const inputRef = useRef(null) - const [searchText, setSearchText] = useState('') + const [{groupChatDids, groupChatProfiles, searchText}, dispatch] = useReducer( + reducer, + { + groupChatDids: [], + groupChatProfiles: [], + searchText: '', + }, + ) const { - data: results, + data: autocompleteResults, isError, - isFetching, + isFetching: isAutocompleteFetching, } = useActorAutocompleteQuery(searchText, true, 12) const {data: follows} = useProfileFollowsQuery(currentAccount?.did) + const {data: memberListData = [], isPending: isMemberListPending} = + useListConvoMembersQuery({ + convoId: convo.view.id, + placeholderData: convo.members, + }) + const memberDidSet = useMemo( + () => new Set(memberListData.map(profile => profile.did)), + [memberListData], + ) - const [{groupChatDids, groupChatProfiles}, dispatch] = useReducer(reducer, { - groupChatDids: [], - groupChatProfiles: [], - }) + // The existing members (including the viewer) already occupy slots, so the + // number of people that can still be added is whatever's left. + const remainingSlots = Math.max( + 0, + convo.details.memberLimit - memberListData.length, + ) const onRemoveDid = useCallback( (did: string) => { @@ -147,8 +182,13 @@ export function AddMembersFlow({ [groupChatDids, groupChatProfiles], ) - const items = useMemo(() => { - let _items: Item[] = [] + const items = useMemo(() => { + if (isMemberListPending) { + // Still fetching chat member DIDs for filtering, so force the loading state. + return [] + } + + const _items: Item[] = [] if (isError) { _items.push({ @@ -157,11 +197,11 @@ export function AddMembersFlow({ message: l`We’re having network issues, try again`, }) } else if (searchText.length) { - if (results?.length) { - for (const profile of results) { + if (autocompleteResults?.length) { + for (const profile of autocompleteResults) { if ( profile.did === currentAccount?.did || - members.includes(profile.did) + memberDidSet.has(profile.did) ) continue _items.push({ @@ -171,21 +211,17 @@ export function AddMembersFlow({ }) } - _items = _items.sort(item => { - return item.type === 'profile' && canBeMessaged(item.profile) ? -1 : 1 + _items.sort(item => { + return item.type === 'profile' && canBeAddedToGroup(item.profile) + ? -1 + : 1 }) } } else { - const placeholders: Item[] = Array(10) - .fill(0) - .map((__, i) => ({ - type: 'placeholder', - key: i + '', - })) - if (follows) { for (const page of follows.pages) { for (const profile of page.follows) { + if (!canBeAddedToGroup(profile)) continue _items.push({ type: 'profile', key: profile.did, @@ -193,16 +229,14 @@ export function AddMembersFlow({ }) } } - - _items = _items.sort(item => { - return item.type === 'profile' && canBeMessaged(item.profile) ? -1 : 1 - }) } else { - _items.push(...placeholders) + for (let i = 0; i < 10; i++) { + _items.push({type: 'placeholder', key: i + ''}) + } } } - if (searchText === '') { + if (searchText === '' && _items.length > 0) { _items.unshift({ type: 'label', key: 'suggested', @@ -210,12 +244,31 @@ export function AddMembersFlow({ }) } - return _items - }, [isError, searchText, l, results, currentAccount?.did, members, follows]) + if (searchText && isAutocompleteFetching && _items.length > 0) { + // Stale results are still showing while autocomplete refetches - + // append an inline indicator so the user sees that work is happening. + _items.push({type: 'loading', key: 'loading'}) + } else if ( + searchText && + !isAutocompleteFetching && + !_items.length && + !isError + ) { + _items.push({type: 'empty', key: 'empty', message: l`No results`}) + } - if (searchText && !isFetching && !items.length && !isError) { - items.push({type: 'empty', key: 'empty', message: l`No results`}) - } + return _items + }, [ + autocompleteResults, + currentAccount?.did, + follows, + isAutocompleteFetching, + isError, + isMemberListPending, + l, + memberDidSet, + searchText, + ]) const handlePressBack = useCallback(() => { control.close() @@ -243,6 +296,13 @@ export function AddMembersFlow({ case 'placeholder': { return } + case 'loading': { + return ( + + + + ) + } case 'empty': { return } @@ -357,7 +417,7 @@ export function AddMembersFlow({ inputRef={inputRef} value={searchText} onChangeText={text => { - setSearchText(text) + dispatch({type: 'setSearchText', searchText: text}) listRef.current?.scrollToOffset({offset: 0, animated: false}) }} onEscape={control.close} @@ -428,6 +488,7 @@ export function AddMembersFlow({ values={groupChatDids} onChange={setGroupChatMembers} type="checkbox" + maxSelections={remainingSlots} label={l`Add group chat members`} style={web([a.contents])}> + + + ) : null + } keyExtractor={(item: Item) => item.key} style={[ web([a.py_0, {height: '100vh', maxHeight: 600}, a.px_0]), native({height: '100%'}), ]} - webInnerContentContainerStyle={[a.py_0, {paddingBottom: footerHeight}]} + contentContainerStyle={items.length === 0 ? {flexGrow: 1} : undefined} + webInnerContentContainerStyle={[ + a.py_0, + {paddingBottom: footerHeight}, + items.length === 0 && {flexGrow: 1}, + ]} webInnerStyle={[a.py_0, {maxWidth: 500, minWidth: 200}]} scrollIndicatorInsets={{top: headerHeight, bottom: footerHeight}} keyboardDismissMode="on-drag" @@ -457,7 +530,6 @@ export function AddMembersFlow({ onPress={handlePressBack}> - {' '} Back diff --git a/src/components/dms/AfterReportConversationDialog.tsx b/src/components/dms/AfterReportConversationDialog.tsx new file mode 100644 index 0000000000..8e484156f4 --- /dev/null +++ b/src/components/dms/AfterReportConversationDialog.tsx @@ -0,0 +1,258 @@ +import {memo, useState} from 'react' +import {View} from 'react-native' +import {type AppBskyActorDefs} from '@atproto/api' +import {Trans, useLingui} from '@lingui/react/macro' +import {StackActions, useNavigation} from '@react-navigation/native' + +import {type NavigationProp} from '#/lib/routes/types' +import {useProfileShadow} from '#/state/cache/profile-shadow' +import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' +import { + useProfileBlockMutationQueue, + useProfileQuery, +} from '#/state/queries/profile' +import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' +import {Button, ButtonText} from '#/components/Button' +import * as Dialog from '#/components/Dialog' +import * as Toggle from '#/components/forms/Toggle' +import {Loader} from '#/components/Loader' +import * as Toast from '#/components/Toast' +import {Text} from '#/components/Typography' +import {IS_NATIVE} from '#/env' + +type ReportDialogParams = { + convoId: string + did: string +} + +/** + * Dialog shown after a report is submitted, allowing the user to block the + * reporter and/or leave the conversation. + */ +export const AfterReportConversationDialog = memo( + function BlockOrLeaveDialogInner({ + control, + params, + currentScreen, + }: { + control: Dialog.DialogControlProps + params: ReportDialogParams + currentScreen: 'list' | 'conversation' + }): React.ReactNode { + const {t: l} = useLingui() + return ( + + + + + + + + ) + }, +) + +function DialogInner({ + params, + currentScreen, +}: { + params: ReportDialogParams + currentScreen: 'list' | 'conversation' +}) { + const t = useTheme() + const {t: l} = useLingui() + const control = Dialog.useDialogContext() + const { + data: profile, + isPending, + isError, + } = useProfileQuery({ + did: params.did, + }) + + return isPending ? ( + + + + ) : isError || !profile ? ( + + + + Report submitted + + + Our moderation team has received your report. + + + + + + ) : ( + + ) +} + +function DoneStep({ + convoId, + currentScreen, + profile, +}: { + convoId: string + currentScreen: 'list' | 'conversation' + profile: AppBskyActorDefs.ProfileViewDetailed +}) { + const {t: l} = useLingui() + const navigation = useNavigation() + const control = Dialog.useDialogContext() + const {gtMobile} = useBreakpoints() + const t = useTheme() + const [actions, setActions] = useState(['block', 'leave']) + const shadow = useProfileShadow(profile) + const [queueBlock] = useProfileBlockMutationQueue(shadow) + + const handleActionsChange = (newActions: string[]) => { + const hadBlock = actions.includes('block') + const hasBlock = newActions.includes('block') + + // If block was just checked, ensure leave is also checked + if (!hadBlock && hasBlock) { + if (!newActions.includes('leave')) { + setActions([...newActions, 'leave']) + } else { + setActions(newActions) + } + } + // If block was just unchecked, also uncheck leave + else if (hadBlock && !hasBlock) { + setActions(newActions.filter(action => action !== 'leave')) + } + // Otherwise, use the new actions as-is (user can toggle leave independently) + else { + setActions(newActions) + } + } + + const {mutate: leaveConvo} = useLeaveConvo(convoId, { + onMutate: () => { + if (currentScreen === 'conversation') { + navigation.dispatch( + StackActions.replace('Messages', IS_NATIVE ? {animation: 'pop'} : {}), + ) + } + }, + onError: () => { + Toast.show(l`Could not leave chat`, { + type: 'error', + }) + }, + }) + + let btnText = l`Done` + let toastMsg: string | undefined + if (actions.includes('leave') && actions.includes('block')) { + btnText = l({ + message: 'Block and leave', + context: 'button', + comment: 'After-report action for a conversation', + }) + toastMsg = l({message: 'Conversation left', context: 'toast'}) + } else if (actions.includes('leave')) { + btnText = l({ + message: 'Leave conversation', + context: 'button', + comment: 'After-report action for a conversation', + }) + toastMsg = l({message: 'Conversation left', context: 'toast'}) + } else if (actions.includes('block')) { + // Shouldn't be able to reach this, but here for completeness. + btnText = l({ + message: 'Block user', + context: 'button', + comment: 'After-report action for a conversation', + }) + toastMsg = l({message: 'User blocked', context: 'toast'}) + } + + const onPressPrimaryAction = () => { + control.close(() => { + if (actions.includes('block')) { + void queueBlock() + } + if (actions.includes('leave')) { + leaveConvo() + } + if (toastMsg) { + Toast.show(toastMsg, { + type: 'success', + }) + } + }) + } + + return ( + + + + Report submitted + + + Our moderation team has received your report. + + + + + + + + Block user + + + + + + Leave conversation + + + + + + + + + + ) +} diff --git a/src/components/dms/AfterReportDialog.tsx b/src/components/dms/AfterReportDialog.tsx index d851037e45..060ea1fcfb 100644 --- a/src/components/dms/AfterReportDialog.tsx +++ b/src/components/dms/AfterReportDialog.tsx @@ -1,9 +1,7 @@ import {memo, useState} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs, type ChatBskyConvoDefs} from '@atproto/api' -import {msg} from '@lingui/core/macro' -import {useLingui} from '@lingui/react' -import {Trans} from '@lingui/react/macro' +import {type AppBskyActorDefs} from '@atproto/api' +import {Trans, useLingui} from '@lingui/react/macro' import {StackActions, useNavigation} from '@react-navigation/native' import {type NavigationProp} from '#/lib/routes/types' @@ -24,7 +22,7 @@ import {IS_NATIVE} from '#/env' type ReportDialogParams = { convoId: string - message: ChatBskyConvoDefs.MessageView + did: string } /** @@ -35,19 +33,22 @@ export const AfterReportDialog = memo(function BlockOrDeleteDialogInner({ control, params, currentScreen, + onClose, }: { control: Dialog.DialogControlProps params: ReportDialogParams currentScreen: 'list' | 'conversation' + onClose?: () => void }): React.ReactNode { - const {_} = useLingui() + const {t: l} = useLingui() return ( - + @@ -64,14 +65,14 @@ function DialogInner({ currentScreen: 'list' | 'conversation' }) { const t = useTheme() - const {_} = useLingui() + const {t: l} = useLingui() const control = Dialog.useDialogContext() const { data: profile, isPending, isError, } = useProfileQuery({ - did: params.message.sender.did, + did: params.did, }) return isPending ? ( @@ -90,7 +91,7 @@ function DialogInner({ + ) +} diff --git a/src/components/dms/ChatInvite/Loading.tsx b/src/components/dms/ChatInvite/Loading.tsx new file mode 100644 index 0000000000..310ed7f1a5 --- /dev/null +++ b/src/components/dms/ChatInvite/Loading.tsx @@ -0,0 +1,18 @@ +import {type StyleProp, View, type ViewStyle} from 'react-native' + +import {atoms as a, useTheme} from '#/alf' +import {Loader} from '#/components/Loader' + +/** + * Loading state for a chat invite: a centered spinner. The outer container + * (height, border, etc.) varies per surface, so pass it via `style`. + */ +export function Loading({style}: {style?: StyleProp}) { + const t = useTheme() + + return ( + + + + ) +} diff --git a/src/components/dms/ChatInvite/Root.tsx b/src/components/dms/ChatInvite/Root.tsx new file mode 100644 index 0000000000..16f5bdfae4 --- /dev/null +++ b/src/components/dms/ChatInvite/Root.tsx @@ -0,0 +1,157 @@ +import {setStringAsync} from 'expo-clipboard' +import {ChatBskyGroupDefs} from '@atproto/api' +import {useLingui} from '@lingui/react/macro' +import {useNavigation} from '@react-navigation/native' + +import {type NavigationProp} from '#/lib/routes/types' +import { + type ChatInvitePreview, + useJoinLinkPreviewsQuery, +} from '#/state/queries/join-links' +import {useSession} from '#/state/session' +import {type ButtonColor} from '#/components/Button' +import {ArrowRight_Stroke2_Corner0_Rounded as ArrowRightIcon} from '#/components/icons/Arrow' +import {ArrowBoxRight_Stroke2_Corner3_Rounded as JoinIcon} from '#/components/icons/ArrowBoxRight' +import {ChainLink_Stroke2_Corner0_Rounded as LinkIcon} from '#/components/icons/ChainLink' +import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check' +import {type Props as SVGIconProps} from '#/components/icons/common' +import {RaisingHand4Finger_Stroke2_Corner2_Rounded as HandIcon} from '#/components/icons/RaisingHand' +import {useIntentDialogs} from '#/components/intents/IntentDialogs' +import * as Toast from '#/components/Toast' +import { + type ChatInviteAction, + ChatInviteProvider, + type ChatInviteStatus, +} from './Context' + +/** + * Headless data + state owner for a chat invite. Fetches the join link preview + * by code and derives the join/open action, exposing both via context for the + * composable parts (`Card`, `JoinButton`) or any custom UI to consume. + * + * Pass `initialPreview` when the preview is already known (e.g. a DM message + * embed already carries the resolved view) to avoid a loading flash. + */ +export function Root({ + code, + initialPreview, + currentConvoId, + hasFixedHeight, + children, +}: { + code: string + initialPreview?: ChatInvitePreview + /** + * The convo this invite is being viewed within, if any. When the invite + * links to the same chat, the action becomes "Copy link" instead of + * open/join (you're already here). + */ + currentConvoId?: string + hasFixedHeight: boolean + children: React.ReactNode +}) { + const {hasSession} = useSession() + const {t: l} = useLingui() + const navigation = useNavigation() + const {groupChatJoinDialogControl, setGroupChatJoinState} = useIntentDialogs() + + const {data, error, isPending} = useJoinLinkPreviewsQuery({ + codes: [code], + hasSession, + // Seed the cache with the already-resolved preview so we don't refetch. + initialData: initialPreview + ? {joinLinkPreviews: [initialPreview]} + : undefined, + }) + + const preview = data?.joinLinkPreviews[0] + + let status: ChatInviteStatus + if (isPending && !preview) { + status = 'loading' + } else if (error) { + status = 'error' + } else if (ChatBskyGroupDefs.isJoinLinkPreviewView(preview)) { + status = 'available' + } else { + // Resolved to a disabled/invalid/unrecognized preview - nothing to join. + status = 'unavailable' + } + + let action: ChatInviteAction | undefined + if (ChatBskyGroupDefs.isJoinLinkPreviewView(preview)) { + const convoId = preview.convo?.id + const isFollowing = preview.owner.viewer?.followedBy ?? false + const hasRequested = !convoId && preview.viewer?.requestedAt != null + + if (convoId && convoId === currentConvoId) { + // You're already in the chat this invite links to - offer to copy the + // link rather than open/join. + action = { + label: l`Copy link`, + accessibilityHint: l`Tap to copy this invite link`, + icon: LinkIcon, + side: 'left', + color: 'primary', + disabled: false, + onPress: () => { + void setStringAsync(`https://bsky.app/chat/${preview.code}`) + Toast.show(l`Copied to clipboard`, {type: 'success'}) + }, + } + } else if (convoId) { + action = { + label: l`Open chat`, + accessibilityHint: l`Tap to open this group chat`, + icon: ArrowRightIcon, + side: 'right', + color: 'primary', + disabled: false, + onPress: () => { + navigation.push('MessagesConversation', {conversation: convoId}) + }, + } + } else { + let canJoin = true + let icon: React.ComponentType = JoinIcon + let label = preview.requireApproval ? l`Request to join` : l`Join` + let color: ButtonColor = 'primary' + if (preview.memberCount >= preview.memberLimit) { + canJoin = false + icon = HandIcon + label = l`This chat is full` + color = 'secondary' + } else if (preview.joinRule === 'followedByOwner' && !isFollowing) { + canJoin = false + icon = HandIcon + label = l`Only people the chat owner follows can join` + color = 'secondary' + } else if (hasRequested) { + icon = CheckIcon + label = l`Requested` + color = 'secondary' + } + + action = { + label, + side: 'left', + accessibilityHint: preview.requireApproval + ? l`Tap to request access to join this group chat` + : l`Tap to join this group chat immediately`, + icon, + color, + disabled: !canJoin, + onPress: () => { + setGroupChatJoinState({code: preview.code}) + groupChatJoinDialogControl.open() + }, + } + } + } + + return ( + + {children} + + ) +} diff --git a/src/components/dms/ChatInvite/Unavailable.tsx b/src/components/dms/ChatInvite/Unavailable.tsx new file mode 100644 index 0000000000..cdfb1d951e --- /dev/null +++ b/src/components/dms/ChatInvite/Unavailable.tsx @@ -0,0 +1,29 @@ +import {type StyleProp, View, type ViewStyle} from 'react-native' +import {Trans} from '@lingui/react/macro' + +import {atoms as a, useTheme} from '#/alf' +import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' +import {Text} from '#/components/Typography' +import {useChatInvite} from './Context' + +/** + * "No longer available" state for a chat invite, shown when the link is + * disabled, invalid, or otherwise can't be resolved. The outer container + * (height, border, etc.) varies per surface, so pass it via `style`. + */ +export function Unavailable({style}: {style?: StyleProp}) { + const t = useTheme() + const {hasFixedHeight} = useChatInvite() + + return ( + + + + Chat invite link no longer available + + + ) +} diff --git a/src/components/dms/ChatInvite/index.tsx b/src/components/dms/ChatInvite/index.tsx new file mode 100644 index 0000000000..a37745616a --- /dev/null +++ b/src/components/dms/ChatInvite/index.tsx @@ -0,0 +1,11 @@ +export {Card} from './Card' +export { + type ChatInviteAction, + type ChatInviteContextValue, + type ChatInviteStatus, + useChatInvite, +} from './Context' +export {JoinButton} from './JoinButton' +export {Loading} from './Loading' +export {Root} from './Root' +export {Unavailable} from './Unavailable' diff --git a/src/components/dms/ConvoMenu.tsx b/src/components/dms/ConvoMenu.tsx index 8ff4cf55bc..2eac8cdb3a 100644 --- a/src/components/dms/ConvoMenu.tsx +++ b/src/components/dms/ConvoMenu.tsx @@ -1,9 +1,7 @@ import {memo, useCallback} from 'react' import {Keyboard, View} from 'react-native' -import {type ChatBskyConvoDefs, type ModerationCause} from '@atproto/api' -import {msg} from '@lingui/core/macro' -import {useLingui} from '@lingui/react' -import {Trans} from '@lingui/react/macro' +import {type ModerationCause} from '@atproto/api' +import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' @@ -18,13 +16,18 @@ import { unstableCacheProfileView, useProfileBlockMutationQueue, } from '#/state/queries/profile' +import {useSession} from '#/state/session' import {type ViewStyleProp} from '#/alf' import {atoms as a} from '#/alf' import {Button, ButtonIcon} from '#/components/Button' +import {AfterReportConversationDialog} from '#/components/dms/AfterReportConversationDialog' import {AfterReportDialog} from '#/components/dms/AfterReportDialog' import {BlockedByListDialog} from '#/components/dms/BlockedByListDialog' import {LeaveConvoPrompt} from '#/components/dms/LeaveConvoPrompt' -import {ReportConversationPrompt} from '#/components/dms/ReportConversationPrompt' +import { + type ConvoWithDetails, + getConvoReportSubject, +} from '#/components/dms/util' import {ArrowBoxLeft_Stroke2_Corner0_Rounded as ArrowBoxLeftIcon} from '#/components/icons/ArrowBoxLeft' import {Bubble_Stroke2_Corner2_Rounded as BubbleIcon} from '#/components/icons/Bubble' import {DotGrid3x1_Stroke2_Corner0_Rounded as DotsHorizontalIcon} from '#/components/icons/DotGrid' @@ -50,10 +53,9 @@ let ConvoMenu = ({ showMarkAsRead, hideTrigger, blockInfo, - latestReportableMessage, style, }: { - convo: ChatBskyConvoDefs.ConvoView + convo: ConvoWithDetails profile: Shadow control?: Menu.MenuControlProps currentScreen: 'list' | 'conversation' @@ -63,25 +65,27 @@ let ConvoMenu = ({ listBlocks: ModerationCause[] userBlock?: ModerationCause } - latestReportableMessage?: ChatBskyConvoDefs.MessageView style?: ViewStyleProp['style'] }): React.ReactNode => { - const {_} = useLingui() + const {t: l} = useLingui() const queryClient = useQueryClient() + const {currentAccount} = useSession() const leaveConvoControl = Prompt.usePromptControl() const reportControl = Prompt.usePromptControl() const blockedByListControl = Prompt.usePromptControl() - const blockOrDeleteControl = Prompt.usePromptControl() + const afterReportControl = Prompt.usePromptControl() const {listBlocks} = blockInfo + const reportSubject = getConvoReportSubject(convo, currentAccount?.did) + return ( <> {!hideTrigger && ( - + {({props}) => ( ) : showButton ? ( + + + + {profile.viewer?.blocking ? ( + + You are blocking {sanitizeHandle(profile.handle, '@')} + + ) : ( + + {sanitizeHandle(profile.handle, '@')} is blocking you + + )} + + + {profile.viewer?.blocking ? ( + + Messages from this person are hidden while you are blocking + them. + + ) : ( + + Messages from this person are hidden while they are blocking + you. + + )} + + + {}} cta={l`Okay`} color="primary" /> + {profile.viewer?.blocking && !profile.viewer.blockingByList && ( + void queueUnblock()} + cta={l`Unblock`} + color="secondary" + /> + )} + + + + + ) +} + +/** + * The "↪ X replied to Y" caption rendered above a reply message, in place of + * the display name. `X` is the person sending the reply (self -> "you"), `Y` is + * the original sender. Tapping it scrolls to the original (if loaded). + * + * Aligns with the sender's display name for others (left), or with the message + * bubble for self (right). + */ +function ReplyCaption({ + replyTo, + isFromSelf, + isGroupChat, + replierDisplayName, + relatedProfiles, + onPress, +}: { + replyTo: ChatBskyConvoDefs.MessageView | ChatBskyConvoDefs.DeletedMessageView + isFromSelf: boolean + isGroupChat: boolean + replierDisplayName: string | null + relatedProfiles: Map + onPress: () => void +}) { + const t = useTheme() + const {t: l} = useLingui() + const {currentAccount} = useSession() + + const originalSenderIsSelf = replyTo.sender.did === currentAccount?.did + const originalProfile = relatedProfiles.get(replyTo.sender.did) + const originalName = originalSenderIsSelf + ? null + : originalProfile + ? createSanitizedDisplayName(originalProfile) + : null + + return ( + + ) +} + +/** + * The nested quote of the original message, rendered at the top of a reply + * bubble. Tapping it scrolls to the original (if loaded). + */ +function ReplyQuote({ + replyTo, + isFromSelf, + relatedProfiles, + onPress, +}: { + replyTo: ChatBskyConvoDefs.MessageView | ChatBskyConvoDefs.DeletedMessageView + isFromSelf: boolean + relatedProfiles: Map + onPress: () => void +}) { + const t = useTheme() + const {t: l} = useLingui() + + const senderProfile = useMaybeProfileShadow( + relatedProfiles.get(replyTo.sender.did), + ) + // Hide the quoted content if we block, or are blocked by, the original + // sender - mirroring how the message bubble itself is hidden. + const isBlocked = senderProfile ? isBlockedOrBlocking(senderProfile) : false + const senderName = + senderProfile && !isBlocked + ? createSanitizedDisplayName(senderProfile) + : null + + const tintColor = isFromSelf ? t.palette.white : t.atoms.text.color + const subtleColor = isFromSelf + ? t.palette.white + : t.atoms.text_contrast_high.color + const borderColor = isFromSelf + ? utils.alpha(t.palette.white, 0.5) + : t.atoms.border_contrast_high.borderColor + + let text: string + let subtle = false + if (isBlocked) { + text = l`Blocked message hidden` + subtle = true + } else if (ChatBskyConvoDefs.isMessageView(replyTo)) { + text = replyTo.text + if (!text.trim()) { + subtle = true + if (ChatBskyEmbedJoinLink.isView(replyTo.embed)) { + text = l`(chat invite link)` + } else if (AppBskyEmbedRecord.isView(replyTo.embed)) { + text = l`(contains embedded content)` + } else { + text = l`No text` + } + } + } else { + text = l`Deleted message` + subtle = true + } + + return ( + + ) +} diff --git a/src/components/dms/MessageItemEmbed.tsx b/src/components/dms/MessageItemEmbed.tsx index 7fce36530d..3b44947f6c 100644 --- a/src/components/dms/MessageItemEmbed.tsx +++ b/src/components/dms/MessageItemEmbed.tsx @@ -6,18 +6,19 @@ import {atoms as a, native, useTheme, web} from '#/alf' import {Embed, PostEmbedViewContext} from '#/components/Post/Embed' import {MessageContextProvider} from './MessageContext' -const CLUSTERED_MESSAGE_GAP = 2 const BORDER_RADIUS = 20 const SQUARED_BORDER_RADIUS = 4 let MessageItemEmbed = ({ embed, isFromSelf, + isGroupChat, squaredTopCorner, squaredBottomCorner, }: { embed: $Typed isFromSelf: boolean + isGroupChat: boolean squaredTopCorner: boolean squaredBottomCorner: boolean }): React.ReactNode => { @@ -28,7 +29,8 @@ let MessageItemEmbed = ({ - + + isFromSelf: boolean + isGroupChat: boolean + squaredTopCorner: boolean + squaredBottomCorner: boolean +}): React.ReactNode => { + const t = useTheme() + const screen = useWindowDimensions() + const convo = useConvoActive() + + const code = isKnownJoinLinkPreview(embed.joinLinkPreview) + ? embed.joinLinkPreview.code + : undefined + if (!code) return null + + return ( + + + + + + + + + + ) +} +MessageItemInviteEmbed = memo(MessageItemInviteEmbed) +export {MessageItemInviteEmbed} + +function MessageItemInviteEmbedBody() { + const {status} = ChatInvite.useChatInvite() + + if (status === 'loading') { + return + } + + if (status !== 'available') { + return + } + + return ( + <> + + + + ) +} diff --git a/src/components/dms/MessageOverlays.tsx b/src/components/dms/MessageOverlays.tsx new file mode 100644 index 0000000000..2384aad88d --- /dev/null +++ b/src/components/dms/MessageOverlays.tsx @@ -0,0 +1,191 @@ +import { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useState, +} from 'react' +import {LayoutAnimation} from 'react-native' +import {type ChatBskyConvoDefs} from '@atproto/api' +import {useLingui} from '@lingui/react/macro' +import {useQueryClient} from '@tanstack/react-query' + +import {useConvoActive} from '#/state/messages/convo' +import {unstableCacheProfileView} from '#/state/queries/unstable-profile-cache' +import {useDialogControl} from '#/components/Dialog' +import {AfterReportDialog} from '#/components/dms/AfterReportDialog' +import {ReactionsDialog} from '#/components/dms/ReactionsDialog' +import {ReportDialog} from '#/components/moderation/ReportDialog' +import * as Prompt from '#/components/Prompt' +import {usePromptControl} from '#/components/Prompt' +import * as Toast from '#/components/Toast' +import type * as bsky from '#/types/bsky' + +type MessageDialogsContextType = { + openDeleteMessage: (message: ChatBskyConvoDefs.MessageView) => void + openReportMessage: ( + message: ChatBskyConvoDefs.MessageView, + senderProfile: bsky.profile.AnyProfileView | undefined, + ) => void + openReactions: (message: ChatBskyConvoDefs.MessageView) => void +} + +const Context = createContext(null) + +export function useMessageDialogs() { + const ctx = useContext(Context) + if (!ctx) { + throw new Error('useMessageDialogs must be used within a MessageOverlays') + } + return ctx +} + +export function MessageOverlays({children}: {children: React.ReactNode}) { + const {t: l} = useLingui() + const queryClient = useQueryClient() + const convo = useConvoActive() + + const deleteControl = usePromptControl() + const reportControl = usePromptControl() + const afterReportControl = usePromptControl() + const reactionsControl = useDialogControl() + + const [deleteTarget, setDeleteTarget] = + useState(null) + const [reportTarget, setReportTarget] = useState<{ + message: ChatBskyConvoDefs.MessageView + senderProfile: bsky.profile.AnyProfileView | undefined + } | null>(null) + const [afterReportTarget, setAfterReportTarget] = + useState(null) + const [reactionsTarget, setReactionsTarget] = + useState(null) + + const openDeleteMessage = useCallback( + (message: ChatBskyConvoDefs.MessageView) => { + setDeleteTarget(message) + deleteControl.open() + }, + [deleteControl], + ) + + const openReportMessage = useCallback( + ( + message: ChatBskyConvoDefs.MessageView, + senderProfile: bsky.profile.AnyProfileView | undefined, + ) => { + setReportTarget({message, senderProfile}) + reportControl.open() + }, + [reportControl], + ) + + const openReactions = useCallback( + (message: ChatBskyConvoDefs.MessageView) => { + setReactionsTarget(message) + }, + [], + ) + + // These dialogs are conditionally mounted, so we can't open them in the same + // tick that we set their targets - the control refs aren't attached yet. Open + // in an effect after the dialog has mounted. + useEffect(() => { + if (reactionsTarget) { + reactionsControl.open() + } + }, [reactionsTarget, reactionsControl]) + + useEffect(() => { + if (afterReportTarget) { + afterReportControl.open() + } + }, [afterReportTarget, afterReportControl]) + + const onConfirmDelete = useCallback(() => { + if (!deleteTarget) return + LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) + convo + .deleteMessage(deleteTarget.id) + .then(() => Toast.show(l({message: 'Message deleted', context: 'toast'}))) + .catch(() => Toast.show(l`Failed to delete message`)) + }, [l, convo, deleteTarget]) + + const onAfterReportSubmit = useCallback(() => { + if (!reportTarget) return + if (reportTarget.senderProfile) { + unstableCacheProfileView(queryClient, reportTarget.senderProfile) + } + setAfterReportTarget(reportTarget.message) + }, [queryClient, reportTarget]) + + const ctx = useMemo( + () => ({openDeleteMessage, openReportMessage, openReactions}), + [openDeleteMessage, openReportMessage, openReactions], + ) + + // `reactionsTarget` is a snapshot from when the dialog was opened. Read the + // live message out of the convo items so optimistic reaction changes (e.g. + // "Tap to remove") are reflected in the dialog without closing it first. + const reactionsMessage = useMemo(() => { + if (!reactionsTarget) return null + for (const item of convo.items) { + if ( + (item.type === 'message' || item.type === 'pending-message') && + item.message.id === reactionsTarget.id + ) { + return item.message + } + } + return reactionsTarget + }, [convo.items, reactionsTarget]) + + const reportSubject = reportTarget + ? ({ + view: 'message', + convoId: convo.convo.view.id, + message: reportTarget.message, + } as const) + : undefined + + return ( + + {children} + setReportTarget(null)} + /> + {afterReportTarget && ( + setAfterReportTarget(null)} + /> + )} + {reactionsMessage && ( + setReactionsTarget(null)} + /> + )} + setDeleteTarget(null)} + /> + + ) +} diff --git a/src/components/dms/MessageReplies.tsx b/src/components/dms/MessageReplies.tsx new file mode 100644 index 0000000000..5093d8d157 --- /dev/null +++ b/src/components/dms/MessageReplies.tsx @@ -0,0 +1,124 @@ +import { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from 'react' +import {type ChatBskyConvoDefs} from '@atproto/api' + +/** + * How long a message stays highlighted after scrolling to it, before the flash + * fades out. + */ +export const MESSAGE_HIGHLIGHT_DURATION_MS = 1500 + +type HighlightedMessage = { + id: string + /** + * Bumped on every highlight so that re-tapping the same reply re-triggers the + * flash even while the previous highlight is still active. + */ + key: number +} + +type MessageRepliesContextType = { + /** + * The message currently staged for reply in the composer, or null. + */ + replyTo: ChatBskyConvoDefs.MessageView | null + setReply: (message: ChatBskyConvoDefs.MessageView) => void + clearReply: () => void + /** + * Scroll the list to a message, if it's currently loaded, and flash it. No-op + * otherwise. + */ + scrollToMessage: (messageId: string) => void + /** + * The message to flash, or null. Consumers compare against their own id. + */ + highlightedMessage: HighlightedMessage | null +} + +const Context = createContext(null) + +export function useMessageReplies() { + const ctx = useContext(Context) + if (!ctx) { + throw new Error( + 'useMessageReplies must be used within a MessageRepliesProvider', + ) + } + return ctx +} + +export function MessageRepliesProvider({ + children, + scrollToMessage: scrollToMessageRaw, +}: { + children: React.ReactNode + /** + * Performs the actual scroll. Returns true if the message was found and + * scrolled to, false if it isn't currently loaded (so we know whether to + * flash it). + */ + scrollToMessage: (messageId: string) => boolean +}) { + const [replyTo, setReplyTo] = useState( + null, + ) + const [highlightedMessage, setHighlightedMessage] = + useState(null) + const highlightKey = useRef(0) + const clearHighlightTimeout = useRef | null>( + null, + ) + + const setReply = useCallback((message: ChatBskyConvoDefs.MessageView) => { + setReplyTo(message) + }, []) + + const clearReply = useCallback(() => { + setReplyTo(null) + }, []) + + const scrollToMessage = useCallback( + (messageId: string) => { + const didScroll = scrollToMessageRaw(messageId) + if (!didScroll) return + + highlightKey.current += 1 + setHighlightedMessage({id: messageId, key: highlightKey.current}) + if (clearHighlightTimeout.current) { + clearTimeout(clearHighlightTimeout.current) + } + clearHighlightTimeout.current = setTimeout(() => { + setHighlightedMessage(null) + }, MESSAGE_HIGHLIGHT_DURATION_MS) + }, + [scrollToMessageRaw], + ) + + useEffect(() => { + return () => { + if (clearHighlightTimeout.current) { + clearTimeout(clearHighlightTimeout.current) + } + } + }, []) + + const ctx = useMemo( + () => ({ + replyTo, + setReply, + clearReply, + scrollToMessage, + highlightedMessage, + }), + [replyTo, setReply, clearReply, scrollToMessage, highlightedMessage], + ) + + return {children} +} diff --git a/src/components/dms/MessagesListBlockedFooter.tsx b/src/components/dms/MessagesListBlockedFooter.tsx index 48886090ac..1fbee421b0 100644 --- a/src/components/dms/MessagesListBlockedFooter.tsx +++ b/src/components/dms/MessagesListBlockedFooter.tsx @@ -1,41 +1,40 @@ import {useCallback, useMemo} from 'react' import {View} from 'react-native' import {type ModerationDecision} from '@atproto/api' -import {msg} from '@lingui/core/macro' -import {useLingui} from '@lingui/react' -import {Trans} from '@lingui/react/macro' +import {Trans, useLingui} from '@lingui/react/macro' import {useProfileShadow} from '#/state/cache/profile-shadow' import {useProfileBlockMutationQueue} from '#/state/queries/profile' -import {atoms as a, useBreakpoints, useTheme} from '#/alf' -import {Button, ButtonText} from '#/components/Button' +import {atoms as a, useTheme} from '#/alf' +import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {useDialogControl} from '#/components/Dialog' -import {Divider} from '#/components/Divider' import {BlockedByListDialog} from '#/components/dms/BlockedByListDialog' import {LeaveConvoPrompt} from '#/components/dms/LeaveConvoPrompt' -import {ReportConversationPrompt} from '#/components/dms/ReportConversationPrompt' +import {ArrowBoxLeft_Stroke2_Corner0_Rounded as LeaveIcon} from '#/components/icons/ArrowBoxLeft' +import { + PersonCheck_Stroke2_Corner0_Rounded as PersonCheckIcon, + PersonX_Stroke2_Corner0_Rounded as PersonXIcon, +} from '#/components/icons/Person' import {Text} from '#/components/Typography' import type * as bsky from '#/types/bsky' export function MessagesListBlockedFooter({ recipient: initialRecipient, convoId, - hasMessages, moderation, + isGroup, }: { recipient: bsky.profile.AnyProfileView convoId: string - hasMessages: boolean moderation: ModerationDecision + isGroup: boolean }) { const t = useTheme() - const {gtMobile} = useBreakpoints() - const {_} = useLingui() + const {t: l} = useLingui() const recipient = useProfileShadow(initialRecipient) - const [__, queueUnblock] = useProfileBlockMutationQueue(recipient) + const [_queueBlock, queueUnblock] = useProfileBlockMutationQueue(recipient) const leaveConvoControl = useDialogControl() - const reportControl = useDialogControl() const blockedByListControl = useDialogControl() const {listBlocks, userBlock} = useMemo(() => { @@ -55,86 +54,78 @@ export function MessagesListBlockedFooter({ if (listBlocks.length) { blockedByListControl.open() } else { - queueUnblock() + void queueUnblock() } }, [blockedByListControl, listBlocks, queueUnblock]) return ( - - - + + + + + {isGroup + ? l`You are blocking the chat owner` + : isBlocking + ? l`You are blocking this person` + : l`This person is blocking you`} + + + You can read chat history but can’t send new messages. + {isBlocking ? ( - You have blocked this user - ) : ( - This user has blocked you - )} - - - + + ) : null} - - {isBlocking && gtMobile && ( - - )} + + - {isBlocking && !gtMobile && ( - - - - )} - - - - - - ) } diff --git a/src/components/dms/MessagesListHeader.tsx b/src/components/dms/MessagesListHeader.tsx index 3f5344faea..dc42965dac 100644 --- a/src/components/dms/MessagesListHeader.tsx +++ b/src/components/dms/MessagesListHeader.tsx @@ -1,23 +1,18 @@ import {useMemo} from 'react' import {View} from 'react-native' -import { - ChatBskyConvoDefs, - moderateProfile, - type ModerationOpts, -} from '@atproto/api' +import {moderateProfile, type ModerationOpts} from '@atproto/api' import {useLingui} from '@lingui/react/macro' -import {useNavigation} from '@react-navigation/native' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' import {makeProfileLink} from '#/lib/routes/links' -import {type NavigationProp} from '#/lib/routes/types' +import {sanitizeHandle} from '#/lib/strings/handles' import {useProfileShadow} from '#/state/cache/profile-shadow' import {useModerationOpts} from '#/state/preferences/moderation-opts' -import {useSession} from '#/state/session' import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar' -import {atoms as a, useTheme} from '#/alf' +import {useIsWithinSplitView} from '#/screens/Messages/components/splitView/context' +import {atoms as a, useTheme, web} from '#/alf' import {AvatarBubbles} from '#/components/AvatarBubbles' -import {Button, ButtonIcon} from '#/components/Button' +import {ButtonIcon} from '#/components/Button' import {ConvoMenu} from '#/components/dms/ConvoMenu' import {Bell2Off_Filled_Corner0_Rounded as BellOffIcon} from '#/components/icons/Bell2' import {DotGrid3x1_Stroke2_Corner0_Rounded as DotsHorizontalIcon} from '#/components/icons/DotGrid' @@ -25,21 +20,24 @@ import * as Layout from '#/components/Layout' import {Link} from '#/components/Link' import {ProfileBadges} from '#/components/ProfileBadges' import {Text} from '#/components/Typography' -import {IS_LIQUID_GLASS, IS_WEB} from '#/env' +import {IS_LIQUID_GLASS} from '#/env' import {type ConvoWithDetails} from './util' -const PFP_SIZE = IS_WEB ? 40 : Layout.HEADER_SLOT_SIZE +const PFP_SIZE = 40 export function MessagesListHeader({convo}: {convo?: ConvoWithDetails | null}) { const t = useTheme() const moderationOpts = useModerationOpts() + const {isWithinSplitView} = useIsWithinSplitView() return ( - - - + {!isWithinSplitView && ( + + + + )} {convo && moderationOpts ? ( convo.kind === 'direct' ? ( @@ -83,8 +81,8 @@ function ProfileHeaderReady({ convo: Extract moderationOpts: ModerationOpts }) { + const t = useTheme() const {t: l} = useLingui() - const {currentAccount} = useSession() const profile = useProfileShadow(convo.primaryMember) const moderation = moderateProfile(profile, moderationOpts) @@ -104,19 +102,14 @@ function ProfileHeaderReady({ const displayName = isDeletedAccount ? l`Deleted Account` : createSanitizedDisplayName(profile, true, moderation.ui('displayName')) - - const latestReportableMessage = - ChatBskyConvoDefs.isMessageView(convo.view.lastMessage) && - convo.view.lastMessage.sender?.did !== currentAccount?.did - ? convo.view.lastMessage - : undefined + const handle = isDeletedAccount ? null : sanitizeHandle(profile.handle, '@') return ( - - - {displayName} - - + + + + {displayName} + + + + + {handle ? ( + + {handle} + + ) : null} } - muted={convo.view.muted} settings={ } /> @@ -153,39 +157,57 @@ function GroupHeaderReady({ }) { const {t: l} = useLingui() - const navigation = useNavigation() - - const handleNavigateToSettings = () => { - navigation.navigate('MessagesConversationSettings', { - conversation: convo.view.id, - }) - } - - const lockStatus = convo.details.lockStatus + const disabled = convo.details.lockStatus === 'locked-permanently' return ( - - - {convo.details.name} - - + + + + + {convo.details.name} + + + + } - muted={convo.view.muted} settings={ - + } /> ) @@ -193,22 +215,28 @@ function GroupHeaderReady({ function Wrapper({ heading, - muted, + settings, }: { heading: React.ReactNode - muted: boolean settings: React.ReactNode }) { return ( - - + + {heading} - - + {settings} diff --git a/src/components/dms/NewMessagesPill.tsx b/src/components/dms/NewMessagesPill.tsx index 7abe528784..fa9520286c 100644 --- a/src/components/dms/NewMessagesPill.tsx +++ b/src/components/dms/NewMessagesPill.tsx @@ -7,7 +7,6 @@ import Animated, { withTiming, } from 'react-native-reanimated' import {useSafeAreaInsets} from 'react-native-safe-area-context' -import {Trans} from '@lingui/react/macro' import { ScaleAndFadeIn, @@ -15,8 +14,8 @@ import { } from '#/lib/custom-animations/ScaleAndFade' import {useHaptics} from '#/lib/haptics' import {atoms as a, useTheme} from '#/alf' -import {Text} from '#/components/Typography' -import {IS_ANDROID, IS_IOS, IS_WEB} from '#/env' +import {ArrowBottom_Stroke2_Corner0_Rounded as ArrowDownIcon} from '#/components/icons/Arrow' +import {IS_WEB} from '#/env' const AnimatedPressable = Animated.createAnimatedComponent(Pressable) @@ -28,8 +27,6 @@ export function NewMessagesPill({ const t = useTheme() const playHaptic = useHaptics() const {bottom: bottomInset} = useSafeAreaInsets() - const bottomBarHeight = IS_IOS ? 42 : IS_ANDROID ? 60 : 0 - const bottomOffset = IS_WEB ? 0 : bottomInset + bottomBarHeight const scale = useSharedValue(1) @@ -60,25 +57,24 @@ export function NewMessagesPill({ a.z_10, a.align_center, { - bottom: bottomOffset + 70, + bottom: bottomInset + 70, // Don't prevent scrolling in this area _except_ for in the pill itself pointerEvents: 'box-none', }, ]}> - - New messages - + ) diff --git a/src/components/dms/ReactionsDialog.tsx b/src/components/dms/ReactionsDialog.tsx index 05233d1c1c..3918143d37 100644 --- a/src/components/dms/ReactionsDialog.tsx +++ b/src/components/dms/ReactionsDialog.tsx @@ -1,4 +1,4 @@ -import {useRef, useState} from 'react' +import {useMemo, useRef, useState} from 'react' import { LayoutAnimation, Pressable, @@ -6,7 +6,6 @@ import { useWindowDimensions, View, } from 'react-native' -import Animated from 'react-native-reanimated' import {type ChatBskyActorDefs, type ChatBskyConvoDefs} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' @@ -15,10 +14,12 @@ import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-disp import {sanitizeHandle} from '#/lib/strings/handles' import {type ActiveConvoStates, useConvoActive} from '#/state/messages/convo' import {useSession} from '#/state/session' +import {type SessionAccount} from '#/state/session/types' import {DraggableScrollView} from '#/view/com/pager/DraggableScrollView' import {UserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, useTheme, web} from '#/alf' import * as Dialog from '#/components/Dialog' +import {filterBlockedReactions} from '#/components/dms/util' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {IS_NATIVE, IS_WEB} from '#/env' @@ -31,18 +32,18 @@ type Reaction = { count: number } +type Tab = Omit + export function ReactionsDialog({ control, relatedProfiles, message, - reactions, - groupedReactions, + onClose, }: { control: Dialog.DialogControlProps relatedProfiles: Map message: ChatBskyConvoDefs.MessageView - reactions?: ChatBskyConvoDefs.ReactionView[] - groupedReactions?: Reaction[] + onClose?: () => void }) { const {t: l} = useLingui() @@ -52,11 +53,13 @@ export function ReactionsDialog({ const [selected, setSelected] = useState('all') - const handleFilter = (value: string) => { - setSelected(value) - } + const reactions = useMemo( + () => filterBlockedReactions(message.reactions, relatedProfiles), + [message.reactions, relatedProfiles], + ) + const groupedReactions = useMemo(() => groupReactions(reactions), [reactions]) - const filteredReactions = reactions?.filter( + const filteredReactions = reactions.filter( r => selected === 'all' || r.value === selected, ) @@ -70,8 +73,8 @@ export function ReactionsDialog({ @@ -80,7 +83,10 @@ export function ReactionsDialog({ return ( setSelected('all')} + onClose={() => { + setSelected('all') + onClose?.() + }} nativeOptions={{ preventExpansion: true, minHeight: screenHeight / 2, @@ -94,7 +100,7 @@ export function ReactionsDialog({ header={IS_WEB ? header : null} style={[web({maxWidth: 400})]}> {filteredReactions - ?.sort((a, b) => { + .sort((a, b) => { if (a.sender.did === currentAccount?.did) return -1 if (b.sender.did === currentAccount?.did) return 1 return 0 @@ -111,7 +117,7 @@ export function ReactionsDialog({ message={message} profile={sender} reaction={reaction} - allReactions={reactions ?? []} + allReactions={reactions} selected={selected} setSelected={setSelected} /> @@ -135,7 +141,7 @@ function ReactionRow({ }: { control: Dialog.DialogControlProps convo: ActiveConvoStates - currentAccount?: bsky.profile.AnyProfileView + currentAccount?: SessionAccount message: ChatBskyConvoDefs.MessageView profile: bsky.profile.AnyProfileView reaction: ChatBskyConvoDefs.ReactionView @@ -149,14 +155,13 @@ function ReactionRow({ const isFromSelf = currentAccount?.did === profile.did const displayName = createSanitizedDisplayName(profile, true) - const handle = sanitizeHandle(profile?.handle ?? '', '@') + const handle = sanitizeHandle(profile.handle, '@') const handleOnPress = () => { - const remainingReactions = - allReactions?.filter( - r => - !(r.value === reaction.value && r.sender.did === currentAccount?.did), - ) ?? [] + const remainingReactions = allReactions.filter( + r => + !(r.value === reaction.value && r.sender.did === currentAccount?.did), + ) if (remainingReactions.length === 0) { control.close() @@ -283,18 +288,13 @@ function ReactionTabs({ tabLayouts.current.set(key, layout) } - const tabs = [ - { - key: 'all', - value: l`All`, - senders: [], - count: totalReactions, - } as Reaction, + const tabs: Tab[] = [ + {key: 'all', value: l`All`, count: totalReactions}, ...(groupedReactions ?? []), ] return ( - + { scrollState.current.width = e.nativeEvent.layout.width }}> - - {tabs?.map((reaction, index) => ( + {tabs.map((tab, index) => ( ))} - + ) @@ -336,14 +336,14 @@ function ReactionTabs({ function ReactionTab({ index, - reaction, + tab, selected, total, onPress, onTabLayout, }: { index: number - reaction: Reaction + tab: Tab selected: string total: number onPress: (value: string) => void @@ -354,11 +354,12 @@ function ReactionTab({ return ( { - onTabLayout(reaction.key, { + onTabLayout(tab.key, { x: e.nativeEvent.layout.x, width: e.nativeEvent.layout.width, }) }} - onPress={() => onPress(reaction.key)}> + onPress={() => onPress(tab.key)}> - {l`${reaction.value} ${reaction.count}`} + {tab.key === 'all' + ? l({ + message: `All ${tab.count}`, + comment: + 'Tab label showing the total count of reactions on a chat message.', + }) + : `${tab.value} ${tab.count}`} ) } + +export function groupReactions( + reactions: ChatBskyConvoDefs.ReactionView[] | undefined, +): Reaction[] { + const grouped = new Map() + for (const reaction of reactions ?? []) { + if (!reaction) continue + const existing = grouped.get(reaction.value) + if (existing) { + existing.senders.push(reaction.sender) + existing.count++ + } else { + grouped.set(reaction.value, { + key: reaction.value, + value: reaction.value, + senders: [reaction.sender], + count: 1, + }) + } + } + return Array.from(grouped.values()) +} diff --git a/src/components/dms/ReportConversationDialog.tsx b/src/components/dms/ReportConversationDialog.tsx new file mode 100644 index 0000000000..2ea38ac0fb --- /dev/null +++ b/src/components/dms/ReportConversationDialog.tsx @@ -0,0 +1,22 @@ +import {type DialogControlProps} from '#/components/Dialog' +import {ReportDialog} from '#/components/moderation/ReportDialog' + +export function ReportConversationDialog({ + control, + convoId, + did, + onAfterSubmit, +}: { + control: DialogControlProps + convoId: string + did: string + onAfterSubmit?: () => void +}) { + return ( + + ) +} diff --git a/src/components/dms/ReportConversationPrompt.tsx b/src/components/dms/ReportConversationPrompt.tsx deleted file mode 100644 index 11c9468ae1..0000000000 --- a/src/components/dms/ReportConversationPrompt.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import {msg} from '@lingui/core/macro' -import {useLingui} from '@lingui/react' - -import {type DialogControlProps} from '#/components/Dialog' -import * as Prompt from '#/components/Prompt' - -export function ReportConversationPrompt({ - control, -}: { - control: DialogControlProps -}) { - const {_} = useLingui() - - return ( - {}} - showCancel={false} - /> - ) -} diff --git a/src/components/dms/SwipeToReply.tsx b/src/components/dms/SwipeToReply.tsx new file mode 100644 index 0000000000..01e9148662 --- /dev/null +++ b/src/components/dms/SwipeToReply.tsx @@ -0,0 +1,195 @@ +import {useMemo} from 'react' +import {View} from 'react-native' +import {Gesture, type GestureType} from 'react-native-gesture-handler' +import Animated, { + clamp, + interpolate, + runOnJS, + useAnimatedStyle, + useReducedMotion, + useSharedValue, + withSequence, + withTiming, +} from 'react-native-reanimated' + +import {useHaptics} from '#/lib/haptics' +import {atoms as a, tokens, useTheme} from '#/alf' +import {ArrowCornerDownRight_Stroke2_Corner3_Rounded as ReplyIcon} from '#/components/icons/ArrowCornerDownRight' + +// Distance the bubble must travel before releasing fires a reply. +const ACTIVATION_THRESHOLD = 56 +// Past the activation point the bubble keeps moving but with exponentially +// increasing resistance, asymptotically approaching this much extra travel. +const MAX_OVERSHOOT = 32 +const ICON_DIAMETER = 32 +// Absurdly high value so the gesture doesn't arm on the vertical axis and +// hijack list scrolling. reanimated/RNGH don't offer clean per-axis disabling. +const EFFECTIVELY_DISABLED_OFFSET = 200 + +// 1:1 up to `threshold`, then exponentially diminishing travel: the marginal +// movement decays the further you pull, easing toward a soft cap of +// `threshold + MAX_OVERSHOOT` rather than hitting a hard wall. +function applyResistance(value: number) { + 'worklet' + const abs = Math.abs(value) + if (abs <= ACTIVATION_THRESHOLD) return value + const sign = value < 0 ? -1 : 1 + const excess = abs - ACTIVATION_THRESHOLD + return ( + sign * + (ACTIVATION_THRESHOLD + + MAX_OVERSHOOT * (1 - Math.exp(-excess / MAX_OVERSHOOT))) + ) +} + +/** + * Swipe a message bubble inward to reply, Signal/WhatsApp style. Self messages + * (right-aligned) swipe left; others' messages (left-aligned) swipe right. A + * reply icon sits stationary in the gutter on the bubble's resting edge and is + * revealed (fade + scale) as the bubble slides away from it. Crossing the + * activation threshold fires a haptic; releasing past it triggers `onReply`. + * + * The pan gesture is built here but handed to `children` via a render prop so + * the consumer can compose it into the message's context-menu gestures with + * `Gesture.Exclusive`. Sharing one arbitration group is what makes the swipe + * and the long-press mutually exclusive - only one can ever win the touch. + */ +export function SwipeToReply({ + isFromSelf, + onReply, + enabled = true, + children, +}: { + isFromSelf: boolean + onReply: () => void + enabled?: boolean + children: (swipeGesture: GestureType) => React.ReactNode +}) { + const t = useTheme() + const playHaptic = useHaptics() + const isReducedMotion = useReducedMotion() + + const transX = useSharedValue(0) + const hit = useSharedValue(false) + const iconScale = useSharedValue(1) + + const swipeGesture = useMemo(() => { + const runPop = () => { + 'worklet' + if (isReducedMotion) return + iconScale.set(() => + withSequence( + withTiming(1.2, {duration: 175}), + withTiming(1, {duration: 100}), + ), + ) + } + + return ( + Gesture.Pan() + .enabled(enabled) + // Arm only on the inward axis; the outward direction is effectively + // disabled so the bubble can't be dragged off its own edge. + .activeOffsetX( + isFromSelf + ? [-10, EFFECTIVELY_DISABLED_OFFSET] + : [-EFFECTIVELY_DISABLED_OFFSET, 10], + ) + .activeOffsetY([ + -EFFECTIVELY_DISABLED_OFFSET, + EFFECTIVELY_DISABLED_OFFSET, + ]) + .onChange(e => { + 'worklet' + const dir = isFromSelf + ? Math.min(e.translationX, 0) + : Math.max(e.translationX, 0) + transX.set(applyResistance(dir)) + + const pastThreshold = Math.abs(transX.get()) >= ACTIVATION_THRESHOLD + if (pastThreshold && !hit.get()) { + hit.set(true) + runPop() + runOnJS(playHaptic)('Medium') + } else if (!pastThreshold && hit.get()) { + hit.set(false) + } + }) + .onEnd(() => { + 'worklet' + // Only a clean end (finger lifted past threshold) triggers the reply. + if (hit.get()) { + runOnJS(onReply)() + } + }) + .onFinalize(() => { + 'worklet' + // Runs on both end and cancellation, so the bubble always animates + // home even if the gesture is interrupted mid-swipe. + transX.set(withTiming(0, {duration: 200})) + hit.set(false) + }) + ) + }, [ + isFromSelf, + enabled, + onReply, + playHaptic, + isReducedMotion, + transX, + hit, + iconScale, + ]) + + const contentStyle = useAnimatedStyle(() => ({ + transform: [{translateX: transX.get()}], + })) + + const iconStyle = useAnimatedStyle(() => { + const progress = clamp(Math.abs(transX.get()) / ACTIVATION_THRESHOLD, 0, 1) + return { + opacity: progress, + transform: [ + {scale: iconScale.get() * interpolate(progress, [0, 1], [0.5, 1])}, + ], + } + }) + + return ( + + + + + + + {/* Sized + aligned to the bubble so the consumer's GestureDetector (the + context menu's, which composes `swipeGesture`) covers only the bubble, + leaving the gutter free for the back/scroll gestures. */} + + {children(swipeGesture)} + + + ) +} diff --git a/src/components/dms/SystemMessageGroup.tsx b/src/components/dms/SystemMessageGroup.tsx new file mode 100644 index 0000000000..930242ca46 --- /dev/null +++ b/src/components/dms/SystemMessageGroup.tsx @@ -0,0 +1,100 @@ +import {Pressable, View} from 'react-native' +import Animated, { + FadeIn, + FadeOut, + LinearTransition, + useAnimatedStyle, + useDerivedValue, + withTiming, +} from 'react-native-reanimated' +import {type ChatBskyActorDefs} from '@atproto/api' +import {plural} from '@lingui/core/macro' +import {useLingui} from '@lingui/react/macro' + +import {HITSLOP_10} from '#/lib/constants' +import {type SystemMessageGroupItem} from '#/screens/Messages/components/groupSystemMessages' +import {atoms as a, useTheme} from '#/alf' +import {SystemMessageItem} from '#/components/dms/SystemMessageItem' +import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron' +import {Text} from '#/components/Typography' + +const ANIMATION_DURATION_MS = 200 + +export function SystemMessageGroup({ + item, + expanded, + onToggle, + relatedProfiles, +}: { + item: SystemMessageGroupItem + expanded: boolean + onToggle: (key: string) => void + relatedProfiles: Map +}) { + const t = useTheme() + const {t: l} = useLingui() + const count = item.items.length + + const label = plural(count, { + one: '# chat update', + other: '# chat updates', + }) + + const rotation = useDerivedValue(() => + withTiming(expanded ? -180 : 0, {duration: ANIMATION_DURATION_MS}), + ) + const chevronStyle = useAnimatedStyle(() => ({ + transform: [{rotate: `${rotation.get()}deg`}], + })) + + return ( + + onToggle(item.key)} + style={[ + a.w_full, + a.flex_row, + a.align_center, + a.justify_center, + a.px_md, + a.mt_md, + ]}> + + {label} + + + + + + + {expanded + ? item.items.map(child => ( + + + + )) + : null} + + + ) +} diff --git a/src/components/dms/SystemMessageItem.tsx b/src/components/dms/SystemMessageItem.tsx index d104af9912..7de6a0cd91 100644 --- a/src/components/dms/SystemMessageItem.tsx +++ b/src/components/dms/SystemMessageItem.tsx @@ -1,25 +1,34 @@ import {View} from 'react-native' +import {type ChatBskyActorDefs} from '@atproto/api' import {useLingui} from '@lingui/react/macro' +import {makeProfileLink} from '#/lib/routes/links' import {type ConvoItem} from '#/state/messages/convo/types' +import {useInviteLinkDialog} from '#/screens/Messages/components/InviteLinkDialogProvider' import {atoms as a, useTheme} from '#/alf' +import {Button} from '#/components/Button' import {getSystemMessageInfo} from '#/components/dms/getSystemMessageInfo' +import {Link} from '#/components/Link' import {Text} from '#/components/Typography' export function SystemMessageItem({ item, + relatedProfiles, }: { item: ConvoItem & {type: 'system-message'} + relatedProfiles: Map }) { const t = useTheme() - const {i18n} = useLingui() + const {i18n, t: l} = useLingui() + const inviteLinkControl = useInviteLinkDialog() - const info = getSystemMessageInfo(item.message.data, item.relatedProfiles) + const info = getSystemMessageInfo(item.message.data, relatedProfiles) if (!info) return null - const {Icon, message} = info + const {Icon, action} = info + const text = i18n._(info.message) - return ( + const row = ( - {i18n._(message)} + ]} + emoji> + {text} ) + + switch (action?.kind) { + case 'profile': + return ( + + {row} + + ) + case 'inviteLink': + if (!inviteLinkControl) return row + return ( + + ) + default: + return row + } } diff --git a/src/components/dms/components/GroupChatProfileCard.tsx b/src/components/dms/components/GroupChatProfileCard.tsx index 49ef5cc5b0..1d704766c2 100644 --- a/src/components/dms/components/GroupChatProfileCard.tsx +++ b/src/components/dms/components/GroupChatProfileCard.tsx @@ -5,7 +5,7 @@ import {Trans} from '@lingui/react/macro' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' import {atoms as a, useTheme} from '#/alf' -import {canBeMessaged} from '#/components/dms/util' +import {canBeAddedToGroup} from '#/components/dms/util' import * as Toggle from '#/components/forms/Toggle' import * as ProfileCard from '#/components/ProfileCard' import {Text} from '#/components/Typography' @@ -19,7 +19,7 @@ export function GroupChatProfileCard({ moderationOpts: ModerationOpts }) { const t = useTheme() - const enabled = canBeMessaged(profile) + const enabled = canBeAddedToGroup(profile) const moderation = moderateProfile(profile, moderationOpts) const handle = sanitizeHandle(profile.handle, '@') const displayName = sanitizeDisplayName( @@ -34,32 +34,40 @@ export function GroupChatProfileCard({ name={profile.did} label={displayName} style={[a.flex_1, a.py_sm, a.px_lg]}> - - - - - - {enabled ? ( - - ) : ( - - {handle} can’t be messaged - - )} + {({disabled, selected}) => ( + <> + + + + + + {enabled ? ( + + ) : ( + + {handle} can’t be added + + )} + + - - - {enabled ? : null} + {enabled ? : null} + + )} ) } diff --git a/src/components/dms/dialogs/NewChatDialog.tsx b/src/components/dms/dialogs/NewChatDialog.tsx index 6686a6ffe2..74ab6b4c0d 100644 --- a/src/components/dms/dialogs/NewChatDialog.tsx +++ b/src/components/dms/dialogs/NewChatDialog.tsx @@ -1,10 +1,16 @@ import {useCallback} from 'react' +import { + ChatBskyConvoGetConvoForMembers, + ChatBskyGroupCreateGroup, +} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification' +import {isNetworkError} from '#/lib/strings/errors' import {logger} from '#/logger' import {useCreateGroupChat} from '#/state/queries/messages/create-group-chat' import {useGetConvoForMembers} from '#/state/queries/messages/get-convo-for-members' +import {useChatActorStatusQuery} from '#/state/queries/messages/get-status' import {FAB} from '#/view/com/util/fab/FAB' import {useTheme} from '#/alf' import * as Dialog from '#/components/Dialog' @@ -17,16 +23,22 @@ import {useAnalytics} from '#/analytics' export function NewChat({ control, onNewChat, + startInGroupChat = false, + onClose, }: { control: Dialog.DialogControlProps onNewChat: (chatId: string) => void + startInGroupChat?: boolean + onClose?: () => void }) { const t = useTheme() const {t: l} = useLingui() const ax = useAnalytics() const requireEmailVerification = useRequireEmailVerification() + const {data: chatStatus} = useChatActorStatusQuery() + const chatDisabled = !!chatStatus?.chatDisabled - const isGroupChatEnabled = ax.features.enabled(ax.features.GroupChatsEnable) + const isGroupChatEnabled = !ax.features.enabled(ax.features.GroupChatsDisable) const {mutate: createChat} = useGetConvoForMembers({ onSuccess: data => { @@ -39,7 +51,32 @@ export function NewChat({ }, onError: error => { logger.error('Failed to create chat', {safeMessage: error}) - Toast.show(l`An issue occurred starting the chat, please try again`, { + let errorMessage = l`An issue occurred starting the chat, please try again.` + if (isNetworkError(error)) { + errorMessage = l`A network error occurred. Please check your internet connection.` + } else if ( + error instanceof ChatBskyConvoGetConvoForMembers.AccountSuspendedError + ) { + errorMessage = l`Suspended accounts cannot participate in chat.` + } else if ( + error instanceof ChatBskyConvoGetConvoForMembers.BlockedActorError + ) { + errorMessage = l`This user has blocked you and cannot be messaged.` + } else if ( + error instanceof ChatBskyConvoGetConvoForMembers.MessagesDisabledError + ) { + errorMessage = l`This user has disabled chat and cannot be messaged.` + } else if ( + error instanceof + ChatBskyConvoGetConvoForMembers.NotFollowedBySenderError + ) { + errorMessage = l`Chat recipient is not followed by the sender.` + } else if ( + error instanceof ChatBskyConvoGetConvoForMembers.RecipientNotFoundError + ) { + errorMessage = l`Unable to find the selected recipient.` + } + Toast.show(errorMessage, { type: 'error', }) }, @@ -52,12 +89,36 @@ export function NewChat({ }, onError: error => { logger.error('Failed to create groupchat', {safeMessage: error}) - Toast.show( - l`An issue occurred creating the group chat, please try again`, - { - type: 'error', - }, - ) + let errorMessage = l`An issue occurred creating the group chat, please try again.` + if (isNetworkError(error)) { + errorMessage = l`A network error occurred. Please check your internet connection.` + } else if ( + error instanceof ChatBskyGroupCreateGroup.AccountSuspendedError + ) { + errorMessage = l`Suspended accounts cannot participate in a group chat.` + } else if (error instanceof ChatBskyGroupCreateGroup.BlockedActorError) { + errorMessage = l`One of the selected recipients has blocked you and cannot be messaged.` + } else if ( + error instanceof + ChatBskyGroupCreateGroup.NewAccountCannotCreateGroupError + ) { + errorMessage = l`You cannot create a group chat yet.` + } else if ( + error instanceof ChatBskyGroupCreateGroup.NotFollowedBySenderError + ) { + errorMessage = l`A selected recipient is not followed by the sender.` + } else if ( + error instanceof ChatBskyGroupCreateGroup.RecipientNotFoundError + ) { + errorMessage = l`Unable to find a selected recipient.` + } else if ( + error instanceof ChatBskyGroupCreateGroup.UserForbidsGroupsError + ) { + errorMessage = l`One of the selected recipients does not allow group chats.` + } + Toast.show(errorMessage, { + type: 'error', + }) }, }) @@ -99,24 +160,31 @@ export function NewChat({ return ( <> - } - accessibilityRole="button" - accessibilityLabel={l`New chat`} - accessibilityHint="" - /> + {!chatDisabled && ( + } + accessibilityRole="button" + accessibilityLabel={l`New chat`} + accessibilityHint="" + /> + )} + nativeOptions={{fullHeight: true}} + onClose={onClose}> {isGroupChatEnabled ? ( ) : ( m.did === did) + return member ? isBlockedOrBlocking(member) : false } export function getMessageInfo({ convo, currentAccountDid, + primaryProfile, i18n, }: { convo: ChatBskyConvoDefs.ConvoView currentAccountDid: string | undefined + primaryProfile?: bsky.profile.AnyProfileView i18n: I18n }): UserMessageInfo | null { if (!ChatBskyConvoDefs.isMessageView(convo.lastMessage)) { @@ -36,6 +70,11 @@ export function getMessageInfo({ const isGroup = ChatBskyConvoDefs.isGroupConvo(convo.kind) const reportableMessage = isFromMe ? undefined : lastMessage + const isBlockedMessage = isDidBlockedInConvo({ + did: senderDid, + members: convo.members, + primaryProfile, + }) const prefix = (message: string) => { if (isFromMe) { @@ -80,6 +119,8 @@ export function getMessageInfo({ } else { message = prefix(defaultEmbeddedContentMessage) } + } else if (ChatBskyEmbedJoinLink.isView(lastMessage.embed)) { + message = prefix(i18n._(msg`(chat invite link)`)) } else { message = prefix(defaultEmbeddedContentMessage) } @@ -89,5 +130,6 @@ export function getMessageInfo({ message, sentAt: lastMessage.sentAt, reportableMessage, + isBlockedMessage, } } diff --git a/src/components/dms/getReactionInfo.ts b/src/components/dms/getReactionInfo.ts index 351c659698..307eebdea7 100644 --- a/src/components/dms/getReactionInfo.ts +++ b/src/components/dms/getReactionInfo.ts @@ -3,19 +3,24 @@ import {type I18n} from '@lingui/core' import {msg} from '@lingui/core/macro' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' +import {isDidBlockedInConvo} from '#/components/dms/getMessageInfo' +import type * as bsky from '#/types/bsky' export type UserReactionInfo = { message: string createdAt: string + isBlocked: boolean } export function getReactionInfo({ convo, currentAccountDid, + primaryProfile, i18n, }: { convo: ChatBskyConvoDefs.ConvoView currentAccountDid: string | undefined + primaryProfile?: bsky.profile.AnyProfileView i18n: I18n }): UserReactionInfo | null { if (!ChatBskyConvoDefs.isMessageAndReactionView(convo.lastReaction)) { @@ -28,6 +33,21 @@ export function getReactionInfo({ const sender = convo.members.find(m => m.did === senderDid) const name = sender ? createSanitizedDisplayName(sender) : null + // Hide the preview when either the reactor or the author of the reacted-to + // message is blocked - otherwise a blocked reactor's name or a blocked + // sender's message text would leak into the chat list. + const isBlocked = + isDidBlockedInConvo({ + did: senderDid, + members: convo.members, + primaryProfile, + }) || + isDidBlockedInConvo({ + did: reactedTo.sender?.did, + members: convo.members, + primaryProfile, + }) + const lastMessageText = reactedTo.text const fallbackMessage = i18n._( msg({ @@ -50,5 +70,6 @@ export function getReactionInfo({ return { message, createdAt: reaction.createdAt, + isBlocked, } } diff --git a/src/components/dms/getSystemMessageInfo.ts b/src/components/dms/getSystemMessageInfo.ts index a0a615f793..756f178566 100644 --- a/src/components/dms/getSystemMessageInfo.ts +++ b/src/components/dms/getSystemMessageInfo.ts @@ -16,74 +16,128 @@ import { } from '#/components/icons/Lock' import {PencilLine_Stroke2_Corner0_Rounded as PencilIcon} from '#/components/icons/Pencil' +export type SystemMessageAction = + | { + kind: 'profile' + profile: ChatBskyActorDefs.ProfileViewBasic + displayName: string + } + | {kind: 'inviteLink'} + export type SystemMessageInfo = { message: MessageDescriptor Icon: React.ComponentType + action?: SystemMessageAction } -function getReferredDisplayName( +function getProfileAction( user: ChatBskyConvoDefs.SystemMessageReferredUser, relatedProfiles: Map, -): string | null { +): Extract | null { const profile = relatedProfiles.get(user.did) - return profile ? createSanitizedDisplayName(profile) : null + if (!profile) return null + return { + kind: 'profile', + profile, + displayName: createSanitizedDisplayName(profile), + } } export function getSystemMessageInfo( data: ChatBskyConvoDefs.SystemMessageView['data'], relatedProfiles: Map, + opts = {short: false}, ): SystemMessageInfo | null { if (ChatBskyConvoDefs.isSystemMessageDataAddMember(data)) { - const name = getReferredDisplayName(data.member, relatedProfiles) + const action = getProfileAction(data.member, relatedProfiles) return { Icon: JoinIcon, - message: name - ? msg`${name} was added to the group` - : msg`Someone was added to the group`, + message: action + ? opts.short + ? msg`${action.displayName} was added` + : msg`${action.displayName} was added to the group` + : opts.short + ? msg`Someone was added` + : msg`Someone was added to the group`, + action: action ?? undefined, } } else if (ChatBskyConvoDefs.isSystemMessageDataRemoveMember(data)) { - const name = getReferredDisplayName(data.member, relatedProfiles) + const action = getProfileAction(data.member, relatedProfiles) return { Icon: LeaveIcon, - message: name - ? msg`${name} was removed from the group` - : msg`Someone was removed from the group`, + message: action + ? opts.short + ? msg`${action.displayName} was removed` + : msg`${action.displayName} was removed from the group` + : opts.short + ? msg`Someone was removed` + : msg`Someone was removed from the group`, + action: action ?? undefined, } } else if (ChatBskyConvoDefs.isSystemMessageDataMemberJoin(data)) { - const name = getReferredDisplayName(data.member, relatedProfiles) + const action = getProfileAction(data.member, relatedProfiles) return { Icon: JoinIcon, - message: name - ? msg`${name} joined the group` - : msg`Someone joined the group`, + message: action + ? opts.short + ? msg`${action.displayName} joined` + : msg`${action.displayName} joined the group` + : opts.short + ? msg`Someone joined` + : msg`Someone joined the group`, + action: action ?? undefined, } } else if (ChatBskyConvoDefs.isSystemMessageDataMemberLeave(data)) { - const name = getReferredDisplayName(data.member, relatedProfiles) + const action = getProfileAction(data.member, relatedProfiles) return { Icon: LeaveIcon, - message: name ? msg`${name} left the group` : msg`Someone left the group`, + message: action + ? opts.short + ? msg`${action.displayName} left` + : msg`${action.displayName} left the group` + : opts.short + ? msg`Someone left` + : msg`Someone left the group`, + action: action ?? undefined, } } else if (ChatBskyConvoDefs.isSystemMessageDataLockConvo(data)) { return {Icon: LockIcon, message: msg`Chat locked`} } else if (ChatBskyConvoDefs.isSystemMessageDataUnlockConvo(data)) { return {Icon: UnlockIcon, message: msg`Chat unlocked`} } else if (ChatBskyConvoDefs.isSystemMessageDataLockConvoPermanently(data)) { - return {Icon: LockIcon, message: msg`Chat locked permanently`} + return {Icon: LockIcon, message: msg`Chat ended`} } else if (ChatBskyConvoDefs.isSystemMessageDataEditGroup(data)) { return { Icon: PencilIcon, - message: data.newName - ? msg`Chat title changed to ${data.newName}` - : msg`Chat title changed`, + message: + data.newName && !opts.short + ? msg`Chat title changed to ${data.newName}` + : msg`Chat title changed`, } } else if (ChatBskyConvoDefs.isSystemMessageDataCreateJoinLink(data)) { - return {Icon: ChainLinkIcon, message: msg`Invite link created`} + return { + Icon: ChainLinkIcon, + message: msg`Invite link created`, + action: {kind: 'inviteLink'}, + } } else if (ChatBskyConvoDefs.isSystemMessageDataEditJoinLink(data)) { - return {Icon: ChainLinkIcon, message: msg`Invite link edited`} + return { + Icon: ChainLinkIcon, + message: msg`Invite link edited`, + action: {kind: 'inviteLink'}, + } } else if (ChatBskyConvoDefs.isSystemMessageDataEnableJoinLink(data)) { - return {Icon: ChainLinkIcon, message: msg`Invite link enabled`} + return { + Icon: ChainLinkIcon, + message: msg`Invite link enabled`, + action: {kind: 'inviteLink'}, + } } else if (ChatBskyConvoDefs.isSystemMessageDataDisableJoinLink(data)) { - return {Icon: ChainLinkBrokenIcon, message: msg`Invite link disabled`} + return { + Icon: ChainLinkBrokenIcon, + message: msg`Invite link disabled`, + action: {kind: 'inviteLink'}, + } } return null } diff --git a/src/components/dms/util.ts b/src/components/dms/util.ts index 5140ba64ca..d37c93430c 100644 --- a/src/components/dms/util.ts +++ b/src/components/dms/util.ts @@ -1,9 +1,22 @@ -import {type $Typed, ChatBskyActorDefs, ChatBskyConvoDefs} from '@atproto/api' +import { + type $Typed, + ChatBskyActorDefs, + ChatBskyConvoDefs, + moderateProfile, + type ModerationOpts, +} from '@atproto/api' import {EMOJI_REACTION_LIMIT} from '#/lib/constants' +import {isBlockedOrBlocking} from '#/lib/moderation/blocked-and-muted' import {logger} from '#/logger' +import {type Shadow} from '#/state/cache/profile-shadow' +import {type ConvoState, ConvoStatus} from '#/state/messages/convo/types' +import {type ReportSubject} from '#/components/moderation/ReportDialog/types' import * as bsky from '#/types/bsky' +export const MESSAGE_GAP_THRESHOLD_MS = 60 * 60 * 1000 +export const CLUSTERED_MESSAGE_THRESHOLD_MS = 5 * 60 * 1000 + export function canBeMessaged(profile: bsky.profile.AnyProfileView) { switch (profile.associated?.chat?.allowIncoming) { case 'none': @@ -21,6 +34,37 @@ export function canBeMessaged(profile: bsky.profile.AnyProfileView) { } } +export function canBeAddedToGroup(profile: bsky.profile.AnyProfileView) { + switch (profile.associated?.chat?.allowGroupInvites) { + case 'none': + return false + case 'all': + return true + case 'following': + return Boolean(profile.viewer?.followedBy) + case undefined: + return canBeMessaged(profile) + default: + return false + } +} + +/** + * Resolves the effective `allowGroupInvites` value for a chat declaration. + * When unset, group invites follow the general DM preference + * (`allowIncoming`), which itself defaults to `following`. This mirrors the + * `undefined` fallthrough in canBeAddedToGroup, and is the single source of + * truth for both displaying and persisting the setting. + */ +export function resolveAllowGroupInvites( + chat: {allowIncoming?: string; allowGroupInvites?: string} | undefined, +): 'all' | 'none' | 'following' { + return (chat?.allowGroupInvites ?? chat?.allowIncoming ?? 'following') as + | 'all' + | 'none' + | 'following' +} + export function localDateString(date: Date) { // can't use toISOString because it should be in local time const mm = date.getMonth() @@ -43,6 +87,25 @@ export function hasAlreadyReacted( ) } +/** + * Drops reactions from accounts the viewer is blocking or blocked by, so a + * blocked reactor's identity is never surfaced via the reaction pills or the + * reactions dialog. `relatedProfiles` is shadow-synced by the convo agent, so + * this reflects optimistic blocks. Reactions whose sender isn't in + * `relatedProfiles` are kept - we can't determine their block status, and they + * already render anonymously ("Someone reacted"). + */ +export function filterBlockedReactions( + reactions: ChatBskyConvoDefs.ReactionView[] | undefined, + relatedProfiles: Map, +): ChatBskyConvoDefs.ReactionView[] { + if (!reactions) return [] + return reactions.filter(reaction => { + const profile = relatedProfiles.get(reaction.sender.did) + return !profile || !isBlockedOrBlocking(profile) + }) +} + export function hasReachedReactionLimit( message: ChatBskyConvoDefs.MessageView, myDid: string | undefined, @@ -56,6 +119,55 @@ export function hasReachedReactionLimit( return myReactions.length >= EMOJI_REACTION_LIMIT } +/** + * Whether the active conversation accepts emoji reactions. Reactions are + * unavailable when: + * - the convo is in the disabled state + * - a group convo is locked or permanently locked + * - 1-1: the other user is blocked or is blocking us + * - group: we are blocking the primary member (the owner) + */ +export function canReact({ + convoState, + primaryMember, + moderationOpts, +}: { + convoState: ConvoState + primaryMember: Shadow | undefined + moderationOpts: ModerationOpts | undefined +}): boolean { + if (convoState.status === ConvoStatus.Disabled) { + return false + } + + if (!convoState.convo) { + return true + } + + if (convoState.convo.kind === 'group') { + const {lockStatus} = convoState.convo.details + if (lockStatus === 'locked' || lockStatus === 'locked-permanently') { + return false + } + } + + if (primaryMember && moderationOpts) { + const moderation = moderateProfile(primaryMember, moderationOpts) + if (convoState.convo.kind === 'direct') { + // Either direction (blocking or blocked-by) hides reactions in 1-1s + if (moderation.blocked) return false + } else { + // In groups, only "we are blocking" the owner hides reactions + const isBlockingPrimary = moderation + .ui('profileView') + .alerts.some(alert => alert.type === 'blocking') + if (isBlockingPrimary) return false + } + } + + return true +} + export type GroupConvoMember = ChatBskyActorDefs.ProfileViewBasic & { // can be missing if account deleted kind?: $Typed @@ -69,7 +181,7 @@ export type ConvoWithDetails = {view: ChatBskyConvoDefs.ConvoView} & ( | { kind: 'group' details: $Typed - primaryMember: GroupConvoMember // the owner + primaryMember?: GroupConvoMember // the owner - may have left, thus optional members: Array } | { @@ -117,11 +229,6 @@ export function parseConvoView( } } - if (!owner) { - logger.warn('No owner found in group convo') - return null - } - return { view: convoView, kind: 'group', @@ -154,3 +261,41 @@ export function parseConvoView( return null } } + +/** + * Resolves the report subject for a conversation-level "Report conversation" + * action (as opposed to reporting an individual message, which always reports + * that message + its sender). + * + * - group: always report the whole convo, targeting the owner. Returns null if + * the owner has left, in which case there is nothing to report against. + * - direct: report the last reportable message if there is one (i.e. the last + * message exists and wasn't sent by us), otherwise report the whole convo + * targeting the other user. + */ +export function getConvoReportSubject( + convo: ConvoWithDetails, + ownDid: string | undefined, +): ReportSubject | null { + if (convo.kind === 'group') { + if (!convo.primaryMember) return null + return {convoId: convo.view.id, did: convo.primaryMember.did} + } + + const lastMessage = convo.view.lastMessage + const reportableMessage = + ChatBskyConvoDefs.isMessageView(lastMessage) && + lastMessage.sender?.did !== ownDid + ? lastMessage + : null + + if (reportableMessage) { + return { + view: 'convo', + convoId: convo.view.id, + message: reportableMessage, + } + } + + return {convoId: convo.view.id, did: convo.primaryMember.did} +} diff --git a/src/components/forms/AutosizedTextarea.tsx b/src/components/forms/AutosizedTextarea.tsx index 402075ea27..d81265f4da 100644 --- a/src/components/forms/AutosizedTextarea.tsx +++ b/src/components/forms/AutosizedTextarea.tsx @@ -1,4 +1,4 @@ -import {useMemo, useRef, useState} from 'react' +import {useEffect, useMemo, useRef, useState} from 'react' import { TextInput, type TextInputContentSizeChangeEvent, @@ -16,6 +16,17 @@ export type AutosizedTextareaProps = Omit & { minRows?: number maxRows?: number onUpdateHeight?: (height: number) => void + /** + * In some cases, like on native, we may not pass in an actual `value` prop. + * This prop allows Android to know if the field was cleared and thereby + * reset its height. This is a small hack required because we need to + * explicitly set the `{height: nativeHeight}` on Android. + * + * If you notice height calcuation issues after clearing the field on + * Android, check if this value is being populated and cleared properly in + * the parent component. + */ + rawValue?: string } export function AutosizedTextarea({ @@ -24,6 +35,7 @@ export function AutosizedTextarea({ minRows = 1, maxRows, onUpdateHeight, + rawValue, onChangeText: onChangeTextOuter, onContentSizeChange: onContentSizeChangeOuter, @@ -124,6 +136,22 @@ export function AutosizedTextarea({ onContentSizeChangeOuter?.(e) } + /* + * Manual height clearing required for Android because we're forced to set + * `{height: nativeHeight}` on Android, and even though `onContentSizeChange` + * fires, the height matches the existing height and we aren't able to reset. + */ + const prevRawValue = useRef(rawValue || '') + useEffect(() => { + if (!IS_ANDROID) return // everything else is fine + if (rawValue === undefined) return // uncontrolled + if (prevRawValue.current?.length && rawValue === '') { + setNativeHeight(minInputHeight) + onUpdateHeight?.(minInputHeight) + } + prevRawValue.current = rawValue + }, [rawValue, minInputHeight]) + return ( - {open && ( // Android implementation of DatePicker currently does not change default button colors according to theme and only takes hex values for buttonColor // Can remove the buttonColor setting if/when this PR is merged: https://github.com/henninghall/react-native-date-picker/pull/871 diff --git a/src/components/forms/Toggle/index.tsx b/src/components/forms/Toggle/index.tsx index 76ab62df66..a62a82b62f 100644 --- a/src/components/forms/Toggle/index.tsx +++ b/src/components/forms/Toggle/index.tsx @@ -212,9 +212,12 @@ export function Item({ ) const highlightStyle = highlightRow - ? selected - ? [a.rounded_full, a.p_md, {backgroundColor: t.palette.primary_50}] - : [a.rounded_full, a.p_md] + ? [ + a.rounded_full, + a.p_md, + hovered && t.atoms.bg_contrast_25, + selected && {backgroundColor: t.palette.primary_50}, + ] : null return ( @@ -572,4 +575,30 @@ export function BaseRadio({ ) } +export function RadioWithLabel({ + label, + selected, +}: { + label: string + selected: boolean +}) { + const t = useTheme() + + return ( + + + + {label} + + + ) +} + export const Platform = IS_NATIVE ? Switch : Checkbox diff --git a/src/components/hooks/useFullscreen.ts b/src/components/hooks/useFullscreen.ts index 107095ef84..3578dd9fc4 100644 --- a/src/components/hooks/useFullscreen.ts +++ b/src/components/hooks/useFullscreen.ts @@ -1,10 +1,4 @@ -import { - useCallback, - useEffect, - useRef, - useState, - useSyncExternalStore, -} from 'react' +import {useCallback, useEffect, useRef, useSyncExternalStore} from 'react' import {IS_WEB, IS_WEB_FIREFOX, IS_WEB_SAFARI} from '#/env' @@ -13,28 +7,38 @@ function fullscreenSubscribe(onChange: () => void) { return () => document.removeEventListener('fullscreenchange', onChange) } +function getFullscreenSnapshot() { + return Boolean(document.fullscreenElement) +} + export function useFullscreen(ref?: React.RefObject) { if (!IS_WEB) throw new Error("'useFullscreen' is a web-only hook") - const isFullscreen = useSyncExternalStore(fullscreenSubscribe, () => - Boolean(document.fullscreenElement), + const isFullscreen = useSyncExternalStore( + fullscreenSubscribe, + getFullscreenSnapshot, ) const scrollYRef = useRef(null) - const [prevIsFullscreen, setPrevIsFullscreen] = useState(isFullscreen) + // Tracked via a ref rather than state so that reacting to a fullscreen change + // never schedules its own render. Scheduling a render in response to the + // external store value (the old `setPrevIsFullscreen` pattern) was a seed for + // the commit-phase update loop reported in APP-315 / APP-5PP / APP-7ZB. + const prevIsFullscreenRef = useRef(isFullscreen) const toggleFullscreen = useCallback(() => { if (isFullscreen) { - document.exitFullscreen() + void document.exitFullscreen() } else { if (!ref) throw new Error('No ref provided') if (!ref.current) return scrollYRef.current = window.scrollY - ref.current.requestFullscreen() + void ref.current.requestFullscreen() } }, [isFullscreen, ref]) useEffect(() => { + const prevIsFullscreen = prevIsFullscreenRef.current if (prevIsFullscreen === isFullscreen) return - setPrevIsFullscreen(isFullscreen) + prevIsFullscreenRef.current = isFullscreen // Chrome has an issue where it doesn't scroll back to the top after exiting fullscreen // Let's play it safe and do it if not FF or Safari, since anything else will probably be chromium @@ -46,7 +50,7 @@ export function useFullscreen(ref?: React.RefObject) { } }, 100) } - }, [isFullscreen, prevIsFullscreen]) + }, [isFullscreen]) return [isFullscreen, toggleFullscreen] as const } diff --git a/src/components/hooks/useStarterPackEntry.native.ts b/src/components/hooks/useLandingEntry.native.ts similarity index 64% rename from src/components/hooks/useStarterPackEntry.native.ts rename to src/components/hooks/useLandingEntry.native.ts index 31221d599d..c5950a6469 100644 --- a/src/components/hooks/useStarterPackEntry.native.ts +++ b/src/components/hooks/useLandingEntry.native.ts @@ -1,22 +1,45 @@ import {useEffect, useState} from 'react' +import * as Linking from 'expo-linking' +import {parseLinkingUrl} from '#/lib/parseLinkingUrl' import { createStarterPackLinkFromAndroidReferrer, httpStarterPackUriToAtUri, } from '#/lib/strings/starter-pack' +import {CHAT_INVITE_CODE_REGEX} from '#/lib/strings/url-helpers' import {useHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs' -import {useSetActiveStarterPack} from '#/state/shell/starter-pack' +import { + useSetActiveLanding, + useSetActiveStarterPack, +} from '#/state/shell/landing' import {IS_ANDROID} from '#/env' import {Referrer, SharedPrefs} from '../../../modules/expo-bluesky-swiss-army' -export function useStarterPackEntry() { +export function useLandingEntry() { const [ready, setReady] = useState(false) const setActiveStarterPack = useSetActiveStarterPack() + const setActiveLanding = useSetActiveLanding() const hasCheckedForStarterPack = useHasCheckedForStarterPack() useEffect(() => { if (ready) return + // Check for group chat invite link from the initial deep link URL + const linkingUrl = Linking.getLinkingURL() + if (linkingUrl) { + const urlp = parseLinkingUrl(linkingUrl) + const chatInviteMatch = urlp.pathname.match(CHAT_INVITE_CODE_REGEX) + if (chatInviteMatch) { + setActiveLanding({ + type: 'groupchat', + uri: linkingUrl, + code: chatInviteMatch[1], + }) + setReady(true) + return + } + } + // On Android, we cannot clear the referral link. It gets stored for 90 days and all we can do is query for it. So, // let's just ensure we never check again after the first time. if (hasCheckedForStarterPack) { @@ -29,7 +52,8 @@ export function useStarterPackEntry() { setReady(true) }, 500) - ;(async () => { + void (async () => { + // Check for starter pack let uri: string | null | undefined if (IS_ANDROID) { @@ -58,7 +82,7 @@ export function useStarterPackEntry() { return () => { clearTimeout(timeout) } - }, [ready, setActiveStarterPack, hasCheckedForStarterPack]) + }, [ready, setActiveStarterPack, setActiveLanding, hasCheckedForStarterPack]) return ready } diff --git a/src/components/hooks/useStarterPackEntry.ts b/src/components/hooks/useLandingEntry.ts similarity index 76% rename from src/components/hooks/useStarterPackEntry.ts rename to src/components/hooks/useLandingEntry.ts index ccbd991574..e3b728a5a0 100644 --- a/src/components/hooks/useStarterPackEntry.ts +++ b/src/components/hooks/useLandingEntry.ts @@ -1,25 +1,27 @@ import {useEffect, useState} from 'react' import {httpStarterPackUriToAtUri} from '#/lib/strings/starter-pack' -import {useSetActiveStarterPack} from '#/state/shell/starter-pack' +import {useSetActiveStarterPack} from '#/state/shell/landing' -export function useStarterPackEntry() { +export function useLandingEntry() { const [ready, setReady] = useState(false) - const setActiveStarterPack = useSetActiveStarterPack() useEffect(() => { const href = window.location.href - const atUri = httpStarterPackUriToAtUri(href) + const url = new URL(href) + // Check for starter pack + const atUri = httpStarterPackUriToAtUri(href) if (atUri) { - const url = new URL(href) // Determines if an App Clip is loading this landing page const isClip = url.searchParams.get('clip') === 'true' setActiveStarterPack({ uri: atUri, isClip, }) + setReady(true) + return } setReady(true) diff --git a/src/components/icons/ArrowCornerDownRight.tsx b/src/components/icons/ArrowCornerDownRight.tsx index 86dde70154..85346f270d 100644 --- a/src/components/icons/ArrowCornerDownRight.tsx +++ b/src/components/icons/ArrowCornerDownRight.tsx @@ -5,3 +5,9 @@ export const ArrowCornerDownRight_Stroke2_Corner2_Rounded = createSinglePathSVG( path: 'M15.793 10.293a1 1 0 0 1 1.338-.068l.076.068 3.293 3.293a2 2 0 0 1 .138 2.677l-.138.151-3.293 3.293a1 1 0 1 1-1.414-1.414L18.086 16H8a5 5 0 0 1-5-5V5a1 1 0 0 1 2 0v6a3 3 0 0 0 3 3h10.086l-2.293-2.293-.068-.076a1 1 0 0 1 .068-1.338Z', }, ) + +export const ArrowCornerDownRight_Stroke2_Corner3_Rounded = createSinglePathSVG( + { + path: 'M5 5a1 1 0 0 0-2 0v4a7 7 0 0 0 7 7h8.086l-2.293 2.293a1 1 0 0 0 1.414 1.414l2.94-2.94a2.5 2.5 0 0 0 0-3.535l-2.94-2.94a1 1 0 1 0-1.414 1.415L18.086 14H10a5 5 0 0 1-5-5V5Z', + }, +) diff --git a/src/components/icons/Bubble.tsx b/src/components/icons/Bubble.tsx index 3654fcf785..8657d8220e 100644 --- a/src/components/icons/Bubble.tsx +++ b/src/components/icons/Bubble.tsx @@ -1,4 +1,4 @@ -import {createSinglePathSVG} from './TEMPLATE' +import {createMultiPathSVG, createSinglePathSVG} from './TEMPLATE' export const BubbleQuestion_Stroke2_Corner0_Rounded = createSinglePathSVG({ path: 'M5.002 17.036V5h14v12.036h-3.986a1 1 0 0 0-.639.23l-2.375 1.968-2.344-1.965a1 1 0 0 0-.643-.233H5.002ZM20.002 3h-16a1 1 0 0 0-1 1v14.036a1 1 0 0 0 1 1h4.65l2.704 2.266a1 1 0 0 0 1.28.004l2.74-2.27h4.626a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1Zm-7.878 3.663c-1.39 0-2.5 1.135-2.5 2.515a1 1 0 0 0 2 0c0-.294.232-.515.5-.515a.507.507 0 0 1 .489.6.174.174 0 0 1-.027.048 1.1 1.1 0 0 1-.267.226c-.508.345-1.128.923-1.286 1.978a1 1 0 1 0 1.978.297.762.762 0 0 1 .14-.359c.063-.086.155-.169.293-.262.436-.297 1.18-.885 1.18-2.013 0-1.38-1.11-2.515-2.5-2.515ZM12 15.75a1.25 1.25 0 1 1 0-2.5 1.25 1.25 0 0 1 0 2.5Z', @@ -15,3 +15,12 @@ export const Bubble_Stroke2_Corner3_Rounded = createSinglePathSVG({ export const Bubbles_Stroke2_Corner2_Rounded = createSinglePathSVG({ path: 'M6.002 6a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-1v1a3 3 0 0 1-3 3h-4.24l-4.274 2.374a1 1 0 0 1-1.486-.874V19a3 3 0 0 1-3-3v-6a3 3 0 0 1 3-3h1V6Zm-1 3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h1a1 1 0 0 1 1 1v.8l3.015-1.674a1 1 0 0 1 .485-.126h4.5a1 1 0 0 0 1-1v-1.933a1 1 0 0 1 0-.134V10a1 1 0 0 0-1-1h-10Zm13 4v-3a3 3 0 0 0-3-3h-7V6a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-1Z', }) + +export const BubbleSmile_Stroke2_Corner2_Rounded_Large = createMultiPathSVG({ + viewBox: '0 0 64 64', + paths: [ + 'M55 32C55 19.298 44.703 9 32 9S9 19.298 9 32c0 3.463.765 6.745 2.134 9.688.358.769.479 1.658.267 2.525l-2.076 8.51a1.667 1.667 0 0 0 1.987 2.02l8.876-2.005a3.84 3.84 0 0 1 2.412.26A22.9 22.9 0 0 0 32 55c12.703 0 23-10.297 23-23Zm2 0c0 13.807-11.193 25-25 25-3.638 0-7.098-.778-10.219-2.177a1.84 1.84 0 0 0-1.152-.133l-8.877 2.004c-2.655.6-5.015-1.802-4.37-4.446l2.077-8.51c.094-.384.046-.809-.139-1.207A24.9 24.9 0 0 1 7 32C7 18.193 18.193 7 32 7s25 11.193 25 25Z', + 'M17.667 32a2.333 2.333 0 1 0 4.667 0 2.333 2.333 0 0 0-4.667 0Zm24 0a2.334 2.334 0 1 0 4.667 0 2.334 2.334 0 0 0-4.667 0Z', + 'M35.137 37.215a1 1 0 0 1 1.414 1.414 6.143 6.143 0 0 1-8.687 0 1 1 0 0 1 1.415-1.414 4.14 4.14 0 0 0 5.858 0Z', + ], +}) diff --git a/src/components/icons/Inbox.tsx b/src/components/icons/Inbox.tsx new file mode 100644 index 0000000000..d6eb34edb5 --- /dev/null +++ b/src/components/icons/Inbox.tsx @@ -0,0 +1,10 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const Inbox_Stroke2_Corner2_Rounded = createSinglePathSVG({ + path: 'M19 14h-2.417a5 5 0 0 1-9.166 0H5v4a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-4Zm0-8a1 1 0 0 0-.898-.995L18 5H6a1 1 0 0 0-1 1v6h3.126a1 1 0 0 1 .969.751 3.001 3.001 0 0 0 5.81 0l.056-.16a1 1 0 0 1 .913-.591H19V6Zm2 12a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V6a3 3 0 0 1 3-3h12l.154.004A3 3 0 0 1 21 6v12Z', +}) + +export const Inbox_Stroke2_Corner2_Rounded_Large = createSinglePathSVG({ + viewBox: '0 0 64 64', + path: 'M57.666 33.416q-.001-.21-.022-.419L57.6 33H44a4.33 4.33 0 0 0-3.466 1.734l-.8 1.066a6.34 6.34 0 0 1-5.068 2.533h-5.333a6.33 6.33 0 0 1-5.066-2.533l-.8-1.066A4.33 4.33 0 0 0 20 33H6.4l-.045-.003a4 4 0 0 0-.022.42v11.916a4.333 4.333 0 0 0 4.333 4.333h42.667a4.333 4.333 0 0 0 4.333-4.333V33.416Zm-38.57-19.083a4.33 4.33 0 0 0-3.764 2.184L7.057 31H20c1.994 0 3.87.939 5.067 2.533l.8 1.067a4.33 4.33 0 0 0 3.466 1.733h5.333a4.33 4.33 0 0 0 3.467-1.733l.8-1.067A6.34 6.34 0 0 1 44 31h12.944l-8.277-14.483a4.33 4.33 0 0 0-3.762-2.184h-25.81Zm40.57 31a6.333 6.333 0 0 1-6.333 6.333H10.666a6.333 6.333 0 0 1-6.333-6.333V33.416c0-1.102.288-2.185.835-3.142l8.428-14.75a6.33 6.33 0 0 1 5.5-3.19h25.809a6.33 6.33 0 0 1 5.499 3.19l8.427 14.75h.001c.547.957.834 2.04.834 3.142v11.917Z', +}) diff --git a/src/components/icons/Language.tsx b/src/components/icons/Language.tsx new file mode 100644 index 0000000000..cacc996ca9 --- /dev/null +++ b/src/components/icons/Language.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const Language_Stroke2_Corner2_Rounded = createSinglePathSVG({ + path: 'M14.5 8a1 1 0 0 1 .912.59l4.5 10a1 1 0 0 1-1.824.82L16.554 16h-4.108l-1.534 3.41a1 1 0 0 1-1.824-.82l4.5-10A1 1 0 0 1 14.5 8m-1.153 6h2.306L14.5 11.437zM5.053.106a1 1 0 0 1 1.342.447L7.118 2H11a1 1 0 0 1 0 2H9.92C9.64 5.765 8.65 7.24 7.527 8.398a17 17 0 0 0 2.605 1.562q.129.062.197.092l.005.002.049.022.01.005a1 1 0 0 1-.787 1.838h-.002l-.027-.013-.068-.03q-.088-.04-.242-.113A18.878 18.878 0 0 1 6 9.75a19 19 0 0 1-3.509 2.125l-.068.03-.027.013h-.002a1 1 0 0 1-.788-1.838l.011-.005.049-.022.005-.002q.069-.03.197-.092a17.176 17.176 0 0 0 2.604-1.562C3.348 7.239 2.36 5.764 2.08 4H1a1 1 0 0 1 0-2h3.882l-.277-.553A1 1 0 0 1 5.053.106M4.118 4C4.388 5.151 5.098 6.193 6 7.1 6.901 6.193 7.612 5.15 7.882 4z', +}) diff --git a/src/components/icons/TEMPLATE.tsx b/src/components/icons/TEMPLATE.tsx index 4ca1674919..a7ef371cd1 100644 --- a/src/components/icons/TEMPLATE.tsx +++ b/src/components/icons/TEMPLATE.tsx @@ -1,8 +1,20 @@ -import {forwardRef} from 'react' +import { + forwardRef, + type ForwardRefExoticComponent, + type RefAttributes, +} from 'react' import Svg, {Path} from 'react-native-svg' import {type Props, useCommonSVGProps} from '#/components/icons/common' +export type IconWithSvgMeta = ForwardRefExoticComponent< + Props & RefAttributes +> & { + svgPaths: string[] + svgViewBox: string + svgStrokeWidth: number +} + export const IconTemplate_Stroke2_Corner0_Rounded = forwardRef( function LogoImpl(props: Props, ref) { const {fill, size, style, ...rest} = useCommonSVGProps(props) @@ -41,7 +53,7 @@ export function createSinglePathSVG({ strokeLinecap?: 'butt' | 'round' | 'square' strokeLinejoin?: 'miter' | 'round' | 'bevel' }) { - return forwardRef(function LogoImpl(props, ref) { + const Icon = forwardRef(function LogoImpl(props, ref) { const {fill, size, style, gradient, ...rest} = useCommonSVGProps(props) const hasStroke = strokeWidth > 0 @@ -51,7 +63,7 @@ export function createSinglePathSVG({ fill="none" {...rest} ref={ref} - viewBox={viewBox || '0 0 24 24'} + viewBox={viewBox ?? '0 0 24 24'} width={size} height={size} style={[style]}> @@ -68,11 +80,21 @@ export function createSinglePathSVG({ /> ) - }) + }) as IconWithSvgMeta + Icon.svgPaths = [path] + Icon.svgViewBox = viewBox || '0 0 24 24' + Icon.svgStrokeWidth = strokeWidth + return Icon } -export function createSinglePathSVG2({path}: {path: string}) { - return forwardRef(function LogoImpl(props, ref) { +export function createMultiPathSVG({ + paths, + viewBox, +}: { + paths: string[] + viewBox?: string +}) { + const Icon = forwardRef(function LogoImpl(props, ref) { const {fill, size, style, gradient, ...rest} = useCommonSVGProps(props) return ( @@ -80,27 +102,7 @@ export function createSinglePathSVG2({path}: {path: string}) { fill="none" {...rest} ref={ref} - viewBox="0 0 24 24" - width={size} - height={size} - style={style}> - {gradient} - - - ) - }) -} - -export function createMultiPathSVG({paths}: {paths: string[]}) { - return forwardRef(function LogoImpl(props, ref) { - const {fill, size, style, gradient, ...rest} = useCommonSVGProps(props) - - return ( - @@ -116,5 +118,9 @@ export function createMultiPathSVG({paths}: {paths: string[]}) { ))} ) - }) + }) as IconWithSvgMeta + Icon.svgPaths = paths + Icon.svgViewBox = viewBox || '0 0 24 24' + Icon.svgStrokeWidth = 0 + return Icon } diff --git a/src/components/icons/Thumb.tsx b/src/components/icons/Thumb.tsx new file mode 100644 index 0000000000..b8247a4d24 --- /dev/null +++ b/src/components/icons/Thumb.tsx @@ -0,0 +1,6 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const ThumbUp_Stroke2_Corner0_Rounded = createSinglePathSVG({ + viewBox: '0 0 21 19', + path: 'M9.61523 0C11.4562 0 12.8635 1.6429 12.5801 3.46191L12.1836 6H16.0635C18.4871 6.0002 20.3536 8.13859 20.0264 10.54L19.3447 15.54C19.0745 17.522 17.3811 19 15.3809 19H2C0.895431 19 0 18.1046 0 17V10C0 8.89543 0.895431 8 2 8H4.38184L8.10547 0.552734L8.17676 0.431641C8.36169 0.163675 8.66854 0 9 0H9.61523ZM6 9.23633V17H15.3809C16.3809 17 17.228 16.2614 17.3633 15.2705L18.0449 10.2705C18.2087 9.06976 17.2753 8.0002 16.0635 8H11.0166C10.7246 8 10.4468 7.87218 10.2568 7.65039C10.0669 7.42849 9.98332 7.13434 10.0283 6.8457L10.6035 3.1543C10.698 2.54844 10.2301 2.00093 9.61719 2L6 9.23633ZM2 17H4V10H2V17Z', +}) diff --git a/src/components/icons/common.tsx b/src/components/icons/common.tsx index 46df59207c..448808f830 100644 --- a/src/components/icons/common.tsx +++ b/src/components/icons/common.tsx @@ -21,6 +21,7 @@ export const sizes = { xl: 28, '2xl': 32, '3xl': 48, + '4xl': 64, } as const export function useCommonSVGProps(props: Props) { diff --git a/src/components/icons/community/Leaflet.tsx b/src/components/icons/community/Leaflet.tsx new file mode 100644 index 0000000000..cba43f85e8 --- /dev/null +++ b/src/components/icons/community/Leaflet.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from '../TEMPLATE' + +export const Leaflet = createSinglePathSVG({ + path: 'M17.058 1.001c.466-.012 1.294.05 1.828.782.099.136.172.274.224.405.385-.036.92-.08 1.416-.039.48.04 1.448.197 2.012 1.09.497.787.372 1.592.13 2.14-.14.315-.345.602-.505.807.172.15.366.354.518.613.366.622.383 1.312.22 1.962-.285 1.143-1.24 1.642-1.82 1.865-.597.23-1.296.354-1.818.448l-.06.01-.121.022c.128.445.101.918-.048 1.367-.41 1.242-1.349 1.831-2.24 2.08-.764.214-1.618.213-2.225.212h-.218c.152.615-.022 1.221-.192 1.631-.63 1.514-2.053 2.08-3.203 2.319s-2.484.247-3.591.253h-.076c-.607.004-1.145.007-1.616.034-.417.806-.763 1.702-1.163 2.75l-.002.002q-.158.414-.327.85c-.446 1.14-1.709 1.693-2.82 1.235-1.11-.459-1.65-1.755-1.204-2.897.571-1.462 1.164-2.48 1.954-4.046.212-.42.39-.76.547-1.075 0-.951.07-1.624.207-2.476.23-1.432.423-1.984.707-3.29.26-.903.644-2.115 1.247-2.783.346-.384.88-.945 1.685-.983.489-.023.794.38 1.052.553.749-1.04 1.384-2.427 2.202-3.503.657-.863 1.363-1.618 2.503-1.438.588.093 1.423.547 1.424 1.438.217-.249.883-1.204 1.162-1.438.532-.445 1.27-.874 2.21-.9m-.42 1.154c-1.003.028-1.71.904-2.303 1.64-.43.532-.8.991-1.18 1.001-.373.01-.345-.247-.31-.572.05-.464.115-1.066-.945-1.234-.879-.14-1.805 1.531-2.61 2.981-.551.993-1.046 1.883-1.428 2.019-.584.208-.854-.037-1.085-.245-.28-.252-.5-.451-1.147.265-.681.755-1.146 2.82-1.596 5.068-.057.287.342.472.525.247 1.036-1.28 2.72-3.039 4.73-4.34 1.848-1.196 3.63-2.385 5.647-2.983.297-.088.38.253.096.377-2.273.994-4.226 2.304-6.272 4.053-2.299 1.923-4.474 4.483-5.577 6.57a27 27 0 0 0-1.829 3.786.724.724 0 0 0 .388.933.693.693 0 0 0 .91-.398q.162-.418.316-.825c.473-1.234.915-2.391 1.539-3.443a.25.25 0 0 1 .187-.123c.678-.082 1.485-.087 2.32-.092 2.277-.013 4.764-.028 5.431-1.632.348-.838-.071-.959-.45-1.067-.31-.089-.593-.17-.406-.629.323-.792 1.377-.793 2.494-.793 1.301 0 2.687 0 3.103-1.254.177-.537-.14-.748-.42-.934-.235-.157-.444-.296-.317-.596.201-.477 1.078-.636 2.007-.803 1.11-.2 2.294-.412 2.49-1.198.186-.744-.16-.983-.52-1.232-.218-.151-.44-.307-.55-.582-.133-.337.158-.697.461-1.071.358-.442.731-.903.438-1.367-.334-.527-1.327-.434-2.15-.358-.512.048-.959.089-1.136-.03-.18-.12-.173-.325-.166-.528.011-.317.022-.63-.685-.611', +}) diff --git a/src/components/icons/community/Offprint.tsx b/src/components/icons/community/Offprint.tsx new file mode 100644 index 0000000000..a52f5843c7 --- /dev/null +++ b/src/components/icons/community/Offprint.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from '../TEMPLATE' + +export const Offprint = createSinglePathSVG({ + path: 'M6.547 11.842c.121.994.564 3.023 1.57 4.733.12.239.442.199.523 0l5.795-10.501a.49.49 0 0 0-.121-.637 4.66 4.66 0 0 0-3.22-1.114c-4.627.12-4.667 5.888-4.547 7.518m6.358 7.796c4.508-.12 4.668-5.847 4.508-7.439-.081-1.034-.524-3.102-1.57-4.892-.08-.16-.362-.16-.443 0l-5.835 10.58c-.08.24-.04.478.121.637a4.66 4.66 0 0 0 3.22 1.114m4.91-16.707-.242.438c-.16.278-.08.596.162.716C20.954 6.113 22 9.614 22 11.92c0 2.864-2.414 9.547-9.779 9.547-1.448 0-2.696-.2-3.823-.557a.52.52 0 0 0-.603.199l-.322.636c-.12.239-.403.319-.644.2l-.443-.24a.46.46 0 0 1-.2-.636l.28-.517c.122-.239.041-.517-.16-.637C3.086 17.848 2 14.308 2 12.08c0-2.864 2.213-9.547 9.618-9.547 1.529 0 2.817.2 3.943.557.242.08.524-.04.644-.238l.362-.637a.457.457 0 0 1 .604-.159l.483.239c.201.12.282.398.16.636', +}) diff --git a/src/components/icons/community/Pckt.tsx b/src/components/icons/community/Pckt.tsx new file mode 100644 index 0000000000..d67eea8a66 --- /dev/null +++ b/src/components/icons/community/Pckt.tsx @@ -0,0 +1,9 @@ +import {createSinglePathSVG} from '../TEMPLATE' + +export const PcktFull = createSinglePathSVG({ + path: 'M4.686 8.752c1.117 0 1.88.517 2.246 1.52q.247.678.249 1.643l-.001.097q-.014.94-.267 1.6l-.074.176c-.394.858-1.123 1.303-2.153 1.303a2.32 2.32 0 0 1-1.408-.455l-.035-.028v1.966a1 1 0 0 1-.008.074c-.029.178-.146.267-.359.267H.854q-.273 0-.335-.216l-.01-.035a1 1 0 0 1-.009-.09v-7.34a.7.7 0 0 1 .02-.149.27.27 0 0 1 .155-.18l.027-.01.005-.002a.5.5 0 0 1 .126-.02h2.066q.04 0 .076.007h.007l.048.013a.27.27 0 0 1 .204.231l.005.028a1 1 0 0 1 .004.07 2 2 0 0 1 .185-.131l.039-.024a2 2 0 0 1 .289-.144q.413-.17.93-.171m5.367.041c.46 0 .958.048 1.655.289l.015.005.021.008.06.021.02.007.018.008.017.007.033.014.024.012.015.008.02.01.003.004.01.005.006.004.01.007.003.003.01.008.005.003.009.008.003.003.009.007.011.012.008.009.004.004.006.008.005.005.005.007.013.023.005.009.004.007.002.003a.4.4 0 0 1 .036.134l.004.05.083 1.389a1 1 0 0 1 0 .083v.01l-.004.03-.007.043-.002.008q0 .006-.004.012v.005l-.005.014-.005.014-.002.003-.005.011-.002.004-.006.01-.002.004-.005.008q0 .003-.003.005l-.003.003-.006.009-.003.004-.018.016-.023.018-.005.003-.01.005h-.002l-.01.006q-.004 0-.008.003l-.007.002-.007.002-.009.003-.007.001-.01.003-.005.001-.013.002H12l-.012.002h-.007l-.013.001h-.048l-.013-.003-.057-.008-.124-.02a9 9 0 0 0-1.272-.112c-.804 0-1.146.265-1.146.857 0 .495.212.857 1.134.857a10 10 0 0 0 1.396-.12c.271-.049.366.06.354.326l-.083 1.388a1 1 0 0 1-.01.093l-.001.008-.003.013-.002.006-.004.012-.018.047-.007.016-.002.002-.006.011-.003.006c-.068.107-.192.153-.358.21a4.5 4.5 0 0 1-1.655.3c-1.24 0-2.135-.377-2.706-.996.306-.61.445-1.365.445-2.219 0-.828-.13-1.562-.418-2.16.574-.6 1.464-.963 2.69-.963m11.419-1.227q.354 0 .354.363v1.073c.386.048.796.116 1.265.21.2.041.327.242.285.446l-.307 1.477a.374.374 0 0 1-.438.293 17 17 0 0 0-.805-.142v.603c0 .724.425.82.71.821.117 0 .33-.024.52-.048.271-.036.39.072.378.338l-.082 1.46c-.012.29-.166.46-.415.52a5.5 5.5 0 0 1-1.24.145c-1.869 0-2.614-.978-2.614-2.982v-.985l-.165.008a.4.4 0 0 1-.262-.085l-.002-.002a.4.4 0 0 1-.04-.039l-.004-.003a.4.4 0 0 1-.05-.071l-.003-.005a.4.4 0 0 1-.023-.052l-.002-.005-.008-.025v-.006a.4.4 0 0 1-.01-.057l-.001-.011v-.006l-.001-.019v-.784c0-.165.07-.322.193-.43.191-.168.493-.435.759-.673.154-.139.296-.268.397-.363l.003-.003.744-.683c.201-.181.438-.278.71-.278zM14.052 7c.249 0 .368.12.368.362v2.52c.375-.199.75-.37 1.143-.512.91-.328 1.852-.47 3.017-.482l-.283.25a1.14 1.14 0 0 0-.385.858v.784q.002.214.083.394c-.294.02-.56.052-.806.096l.01.029c.436 1.35 1.099 3.357 1.099 3.357.082.254-.013.386-.273.386h-2.01a.524.524 0 0 1-.532-.386l-.77-2.338q-.142.09-.293.188v2.174q.002.362-.367.362h-1.665a.95.95 0 0 0 .23-.6l.083-1.39v-.007c.008-.165-.001-.484-.243-.718-.248-.24-.564-.225-.748-.194l-.033.005v-.355l.033.006c.197.035.521.046.77-.208.23-.234.24-.544.233-.713v-.009l-.083-1.388a.93.93 0 0 0-.317-.685 1.3 1.3 0 0 0-.382-.209l-.093-.032-.161-.054V7.362q0-.362.354-.362zM3.919 10.868c-.556 0-.74.423-.74 1.132s.184 1.132.74 1.132.738-.423.738-1.132-.182-1.132-.738-1.132', +}) + +export const Pckt = createSinglePathSVG({ + path: 'M13.004 1.646c1.983 0 3.668.678 4.824 2.135q.17.215.32.446.694.46 1.24 1.146C20.512 6.787 21 8.767 21 11.103c0 1.856-.31 3.49-.999 4.795q.105.073.201.163c.306.288.483.648.595.967l.007.019.006.02c.144.46.24 1.13-.137 1.75a1.8 1.8 0 0 1-.263.335q.031.196.017.413c-.054.732-.522 1.223-.926 1.516H19.5c-.3.218-.73.458-1.268.458-.311 0-.58-.078-.807-.201-.23.123-.5.201-.81.201-.515 0-.955-.24-1.256-.458l-.013-.01c-.333-.249-.735-.648-.873-1.224a2.6 2.6 0 0 1-.673-.357l-.014-.01a2.6 2.6 0 0 1-.617-.64 6 6 0 0 1-.48-.128v2.168c0 .439-.108.995-.543 1.428-.429.427-.972.53-1.4.53H6.48c-.43 0-.968-.111-1.39-.541a1.87 1.87 0 0 1-.506-1.074 1.8 1.8 0 0 1-1.053-.517c-.421-.43-.53-.978-.53-1.417V3.86c0-.44.109-.987.53-1.418s.96-.54 1.389-.54h4.267c.348 0 .767.067 1.14.315.798-.376 1.7-.57 2.678-.57m0 1.193c-1.223 0-2.246.357-3.045.993 0-.483-.25-.738-.773-.738H4.918q-.748 0-.75.763V19.29q.002.763.75.764h4.268c.523 0 .773-.255.773-.764v-4.1c.81.625 1.814 1.005 2.985 1.018.007-.19.048-.377.107-.552.086-.277.218-.548.432-.753a1.17 1.17 0 0 1 .954-.318 1.25 1.25 0 0 1 .267-.548c.304-.355.749-.455 1.165-.455.411 0 .858.1 1.16.458q.064.076.112.156c.744-1.115 1.129-2.685 1.129-4.684 0-4.38-1.847-6.672-5.266-6.672m2.865 11.34c-.684 0-.955.332-.873 1.008l.047.248-.224-.119a1.2 1.2 0 0 0-.508-.142c-.341 0-.566.214-.707.676-.213.628.024.995.696 1.114l.248.036-.177.201c-.483.474-.448.9.106 1.316.212.153.413.237.578.237.271 0 .507-.19.696-.557l.118-.225.118.225c.189.367.414.557.685.557.189 0 .377-.083.59-.237.554-.403.578-.842.106-1.328L17.203 17l.236-.036c.684-.119.896-.486.696-1.126-.154-.438-.378-.651-.697-.651-.153 0-.318.047-.518.141l-.236.107.035-.248c.106-.676-.177-1.008-.85-1.008m-4.485-6.877c1.173 0 1.56.893 1.56 2.386s-.387 2.388-1.56 2.388-1.56-.894-1.56-2.388.387-2.386 1.56-2.386', +}) diff --git a/src/components/icons/community/StandardSite.tsx b/src/components/icons/community/StandardSite.tsx new file mode 100644 index 0000000000..86e5731bb7 --- /dev/null +++ b/src/components/icons/community/StandardSite.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from '../TEMPLATE' + +export const StandardSite = createSinglePathSVG({ + path: 'M11.815.056a.33.33 0 0 1 .37 0c.13.088.2.95.34 2.673l.047.599h3.312a4.745 4.745 0 0 1 4.745 4.745v.818h-1.637v-.818a3.11 3.11 0 0 0-3.109-3.11h-3.175c.147 1.658.295 2.622.726 3.41a5.24 5.24 0 0 0 2.026 2.05c.969.545 2.214.667 4.702.91l1.366.133c1.553.151 2.33.227 2.417.357a.32.32 0 0 1-.003.364c-.088.128-.866.192-2.422.32l-.891.073v3.347a4.745 4.745 0 0 1-4.746 4.746h-3.315l-.044.56c-.139 1.748-.21 2.622-.34 2.711a.33.33 0 0 1-.369 0c-.13-.088-.2-.963-.339-2.711l-.045-.56H8.03a4.746 4.746 0 0 1-4.746-4.746v-.818H4.92v.818a3.11 3.11 0 0 0 3.11 3.11h3.269c-.15-1.72-.297-2.713-.743-3.52a5.24 5.24 0 0 0-2.064-2.057c-.987-.543-2.254-.646-4.786-.853l-1.227-.1c-1.555-.128-2.333-.192-2.422-.32a.325.325 0 0 1-.003-.364c.087-.13.864-.206 2.417-.357l.812-.08V8.073a4.746 4.746 0 0 1 4.746-4.745h3.399l.048-.599c.14-1.722.209-2.585.34-2.673m7.177 12.66c-1.697.152-2.682.303-3.484.744a5.24 5.24 0 0 0-2.065 2.057c-.445.807-.592 1.8-.741 3.52h3.182a3.11 3.11 0 0 0 3.108-3.11zM8.029 4.964a3.11 3.11 0 0 0-3.11 3.109v3.152c1.79-.184 2.803-.342 3.62-.803a5.24 5.24 0 0 0 2.027-2.048c.432-.79.58-1.753.727-3.41z', +}) diff --git a/src/components/images/AutoSizedImage.tsx b/src/components/images/AutoSizedImage.tsx index 2df8d056d6..182bfe661b 100644 --- a/src/components/images/AutoSizedImage.tsx +++ b/src/components/images/AutoSizedImage.tsx @@ -1,4 +1,4 @@ -import {useMemo, useRef} from 'react' +import {useEffect, useMemo, useRef} from 'react' import {type DimensionValue, Pressable, View} from 'react-native' import Animated, { type AnimatedRef, @@ -69,6 +69,8 @@ export function AutoSizedImage({ onPress, onLongPress, onPressIn, + onContainerRef, + onDimsChange, }: { image: AppBskyEmbedImages.ViewImage crop?: 'none' | 'square' | 'constrained' @@ -79,6 +81,11 @@ export function AutoSizedImage({ ) => void onLongPress?: () => void onPressIn?: () => void + /** Fires once with the internal container ref so a parent can drive its + * own lightbox-return animation without waiting for an `onPress`. */ + onContainerRef?: (ref: AnimatedRef) => void + /** Fires when the underlying image reports its natural dimensions. */ + onDimsChange?: (dims: Dimensions) => void }) { const t = useTheme() const {_} = useLingui() @@ -86,6 +93,10 @@ export function AutoSizedImage({ const containerRef = useAnimatedRef() const fetchedDimsRef = useRef<{width: number; height: number} | null>(null) + useEffect(() => { + onContainerRef?.(containerRef) + }, [containerRef, onContainerRef]) + let aspectRatio: number | undefined const dims = image.aspectRatio if (dims) { @@ -122,13 +133,16 @@ export function AutoSizedImage({ accessibilityHint="" onLoad={e => { if (!isContain) { - fetchedDimsRef.current = { + const dims = { width: e.source.width, height: e.source.height, } + fetchedDimsRef.current = dims + onDimsChange?.(dims) } }} loading="lazy" + useAppleWebpCodec /> diff --git a/src/components/images/Gallery/index.tsx b/src/components/images/Gallery/index.tsx index 1566f09736..5b0d97bc17 100644 --- a/src/components/images/Gallery/index.tsx +++ b/src/components/images/Gallery/index.tsx @@ -24,7 +24,7 @@ import {mergeRefs} from '#/lib/merge-refs' import {useA11y} from '#/state/a11y' import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge' import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture' -import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' +import {atoms as a, tokens, useBreakpoints, useTheme, web} from '#/alf' import {ArrowsDiagonalOut_Stroke2_Corner0_Rounded as Fullscreen} from '#/components/icons/ArrowsDiagonal' import {AutoSizedImage} from '#/components/images/AutoSizedImage' import { @@ -36,10 +36,11 @@ import {useKeyboardHandlers} from '#/components/images/Gallery/useKeyboardHandle import {usePointerHandlers} from '#/components/images/Gallery/usePointerHandlers' import {getAspectRatio} from '#/components/images/Gallery/utils' import {MediaInsetBorder} from '#/components/MediaInsetBorder' +import {ImageContextMenu} from '#/components/Post/Embed/ImageContextMenu' import {PostEmbedViewContext} from '#/components/Post/Embed/types' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' -import {IS_WEB} from '#/env' +import {IS_ANDROID, IS_WEB} from '#/env' export * from './const' export * from './maybeApplyGalleryOffsetStyles' @@ -53,6 +54,14 @@ interface GalleryProps { ) => void onPressIn?: (index: number) => void viewContext?: PostEmbedViewContext + isWithinQuote?: boolean + // Post context for the in-feed carousel swipe metric. Omit for non-post + // contexts (no event will be emitted). + metricsPostContext?: { + postUri: string + postAuthorDid: string + feedDescriptor?: string + } } const Context = createContext<{ @@ -96,6 +105,8 @@ export function Gallery({ onPress, onPressIn, viewContext, + isWithinQuote, + metricsPostContext, }: GalleryProps) { const {t: l} = useLingui() const ax = useAnalytics() @@ -103,14 +114,21 @@ export function Gallery({ const largeAltBadge = useLargeAltBadgeEnabled() const bps = useBreakpoints() const window = useWindowDimensions() - const isWithinQuote = - viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia const isWithinChat = viewContext === PostEmbedViewContext.ChatMessage const hideBadges = isWithinQuote const contentHeight = useMemo(() => { if (isWithinChat) { return 120 } + if (isWithinQuote) { + if (bps.gtMobile) { + return 220 + } else if (bps.gtPhone) { + return 190 + } else { + return 150 + } + } if (bps.gtMobile) { return 300 } else if (bps.gtPhone) { @@ -118,7 +136,7 @@ export function Gallery({ } else { return 200 } - }, [bps, isWithinChat]) + }, [bps, isWithinChat, isWithinQuote]) /* * Container overflow styles @@ -159,13 +177,17 @@ export function Gallery({ const emitSwipeMetric = useMemo( () => debounce((fromIndex: number, toIndex: number) => { - ax.metric('post:gallery:swipe', { + if (!metricsPostContext) return + ax.metric('post:photoEmbed:carouselSwipe', { fromImage: fromIndex + 1, // convert to 1-based index for easier analysis toImage: toIndex + 1, // convert to 1-based index for easier analysis totalImages: images.length, + postUri: metricsPostContext.postUri, + postAuthorDid: metricsPostContext.postAuthorDid, + feedDescriptor: metricsPostContext.feedDescriptor, }) }, 200), - [ax, images.length], + [ax, images.length, metricsPostContext], ) const setCurrentIndex = (index: number) => { @@ -219,7 +241,7 @@ export function Gallery({ crop={ viewContext === PostEmbedViewContext.ThreadHighlighted ? 'none' - : viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia + : isWithinQuote ? 'square' : 'constrained' } @@ -228,9 +250,7 @@ export function Gallery({ onPress?.(index, [containerRef], [dims]) } onPressIn={() => onPressIn?.(index)} - hideBadge={ - viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia - } + hideBadge={isWithinQuote} /> ))} @@ -256,6 +276,9 @@ export function Gallery({ aria-label={l`Image gallery, ${images.length} images`} horizontal pagingEnabled={false} + // Disable Android's stretch overscroll, which can leave the carousel + // settled just off the left edge instead of aligned to x = 0 + overScrollMode={IS_ANDROID ? 'never' : 'auto'} showsHorizontalScrollIndicator={false} directionalLockEnabled nestedScrollEnabled @@ -264,6 +287,17 @@ export function Gallery({ data={images} keyExtractor={(item, index) => item.thumb + index} renderItem={({item, index}) => { + const openLightboxAtIndex = onPress + ? () => { + const refs: AnimatedRef[] = [] + const dims: (Dimensions | null)[] = [] + for (let i = 0; i < images.length; i++) { + refs.push(containerRefsRef.current.get(i)!) + dims.push(thumbDimsRef.current.get(i) ?? null) + } + onPress(index, refs, dims) + } + : undefined return ( { thumbDimsRef.current.set(i, dims) }} - onPress={ - onPress - ? () => { - ax.metric('post:gallery:openLightbox', { - fromImage: index + 1, // convert to 1-based index for easier analysis - totalImages: images.length, - }) - const refs: AnimatedRef[] = [] - const dims: (Dimensions | null)[] = [] - for (let i = 0; i < images.length; i++) { - refs.push(containerRefsRef.current.get(i)!) - dims.push(thumbDimsRef.current.get(i) ?? null) - } - onPress(index, refs, dims) - } - : undefined - } + onPress={openLightboxAtIndex} onPressIn={onPressIn ? () => onPressIn(index) : undefined} + onPreviewPress={openLightboxAtIndex} /> ) }} @@ -332,6 +351,11 @@ export function Gallery({ marginLeft: -insetLeft, width, }, + // Prevent horizontal trackpad/wheel swipes from triggering the + // browser's back/forward overscroll-navigation gesture. Handles + // Chrome and Firefox; Safari is handled via the wheel listener in + // usePointerHandlers.web.ts since it ignores overscroll-behavior. + web({overscrollBehaviorX: 'contain'}), ]} contentContainerStyle={{ gap: ITEM_GAP, @@ -378,6 +402,7 @@ function GalleryImage({ onThumbDims, onPress, onPressIn, + onPreviewPress, }: { contentHeight: number image: AppBskyEmbedImages.ViewImage @@ -391,6 +416,7 @@ function GalleryImage({ onThumbDims: (index: number, dims: Dimensions) => void onPress?: () => void onPressIn?: () => void + onPreviewPress?: () => void }) { const t = useTheme() const {t: l} = useLingui() @@ -416,124 +442,165 @@ function GalleryImage({ collapsable={false} aria-roledescription={l`slide`} aria-label={image.alt || l`Image ${index + 1} of ${imageCount}`}> - setFocused(true)} - onBlur={() => setFocused(false)} - accessibilityRole="button" - accessibilityLabel={image.alt || l`Image ${index + 1}`} - accessibilityHint={l`Opens full image`} - android_ripple={{ - color: utils.alpha(t.atoms.bg.backgroundColor, 0.2), - foreground: true, - }} - style={({pressed}) => [ - a.rounded_md, - a.overflow_hidden, - t.atoms.bg_contrast_25, - web([ - { - cursor: 'inherit', - outline: 0, - border: 0, - }, - a.transition_transform, - {transitionDuration: '200ms'}, - pressed && {transform: [{scale: 0.99}]}, - ]), - ]}> - { - const ar = getAspectRatio(e.source) - if (ar && ar !== aspectRatio) { - setAspectRatio(ar) - } - onThumbDims(index, { - width: e.source.width, - height: e.source.height, - }) + + setFocused(true)} + onBlur={() => setFocused(false)} + accessibilityRole="button" + accessibilityLabel={image.alt || l`Image ${index + 1}`} + accessibilityHint={l`Opens full image`} + android_ripple={{ + color: utils.alpha(t.atoms.bg.backgroundColor, 0.2), + foreground: true, }} - /> - - {(hasAlt || isCropped) && !hideBadges ? ( - [ + a.rounded_md, + a.overflow_hidden, + t.atoms.bg_contrast_25, + web([ { - bottom: a.p_xs.padding, - right: a.p_xs.padding, - gap: 3, + cursor: 'inherit', + outline: 0, + border: 0, }, - largeAltBadge && { - gap: 4, - }, - ]}> - {isCropped && ( - - - - )} - {hasAlt && ( - - - ALT - - - )} - - ) : null} + a.transition_transform, + {transitionDuration: '200ms'}, + pressed && {transform: [{scale: 0.99}]}, + ]), + ]}> + { + const ar = getAspectRatio(e.source) + if (ar && ar !== aspectRatio) { + setAspectRatio(ar) + } + onThumbDims(index, { + width: e.source.width, + height: e.source.height, + }) + }} + useAppleWebpCodec + /> - 1 ? ( + + + + {index + 1}/{imageCount} + + + + ) : null} + + {(hasAlt || isCropped) && !hideBadges ? ( + + {isCropped && ( + + + + )} + {hasAlt && ( + + + ALT + + + )} + + ) : null} + + - + /> + + ) } diff --git a/src/components/images/Gallery/maybeApplyGalleryOffsetStyles.ts b/src/components/images/Gallery/maybeApplyGalleryOffsetStyles.ts index 5081b5a2d5..6ac2c0086f 100644 --- a/src/components/images/Gallery/maybeApplyGalleryOffsetStyles.ts +++ b/src/components/images/Gallery/maybeApplyGalleryOffsetStyles.ts @@ -1,4 +1,5 @@ import { + AppBskyEmbedGallery, AppBskyEmbedImages, AppBskyEmbedRecordWithMedia, type AppBskyFeedDefs, @@ -27,9 +28,6 @@ export function maybeApplyGalleryOffsetStyles( additionalCauses?: ModerationCause[] | AppModerationCause[] }, ) { - // don't ever check gates like this, except this one time - if (!features.isOn(Features.PostGalleryEmbedEnable)) return - if ( !bsky.dangerousIsType( post.record, @@ -39,6 +37,13 @@ export function maybeApplyGalleryOffsetStyles( return } + // The gate only controls whether legacy image embeds opt into the new + // expanded gallery layout. Gallery embeds always render expanded by item + // count, so their offset must apply regardless of the gate. + const isPostGalleryEmbedEnabled = features.isOn( + Features.PostGalleryEmbedEnable, + ) + /* * First check if we even have images */ @@ -49,6 +54,12 @@ export function maybeApplyGalleryOffsetStyles( embed, AppBskyEmbedImages.isMain, ) + const isGalleryEmbed = + embed && + bsky.dangerousIsType( + embed, + AppBskyEmbedGallery.isMain, + ) const isRecordWithMedia = embed && bsky.dangerousIsType( @@ -57,10 +68,16 @@ export function maybeApplyGalleryOffsetStyles( ) let hasImages = false if (isImageEmbed) { + if (!isPostGalleryEmbedEnabled) return // one image, not a gallery if (embed.images.length === 1) return hasImages = true } + if (isGalleryEmbed) { + // single (or empty) gallery - no offset needed + if (embed.items.length <= 1) return + hasImages = true + } if (isRecordWithMedia) { if ( bsky.dangerousIsType( @@ -68,9 +85,19 @@ export function maybeApplyGalleryOffsetStyles( AppBskyEmbedImages.isMain, ) ) { + if (!isPostGalleryEmbedEnabled) return // one image, not a gallery if (embed.media.images.length === 1) return } + if ( + bsky.dangerousIsType( + embed.media, + AppBskyEmbedGallery.isMain, + ) + ) { + // single (or empty) gallery - no offset needed + if (embed.media.items.length <= 1) return + } hasImages = true } if (!hasImages) return diff --git a/src/components/images/Gallery/usePointerHandlers.web.ts b/src/components/images/Gallery/usePointerHandlers.web.ts index 25bebfccbd..ddf9bdf0c6 100644 --- a/src/components/images/Gallery/usePointerHandlers.web.ts +++ b/src/components/images/Gallery/usePointerHandlers.web.ts @@ -4,6 +4,7 @@ import {type FlatList} from 'react-native' import {ITEM_GAP} from '#/components/images/Gallery/const' import {tween} from '#/components/images/Gallery/tween' import {getOffsetForIndex} from '#/components/images/Gallery/utils' +import {IS_WEB_SAFARI} from '#/env' const DRAG_THRESHOLD = 3 const FLICK_DECAY = 0.85 @@ -246,12 +247,64 @@ export function usePointerHandlers({ } } + /* + * Safari does not support `overscroll-behavior`, so a horizontal trackpad + * swipe over the carousel can trigger the browser's back/forward + * navigation gesture. We intercept predominantly-horizontal wheel events + * and apply the scroll ourselves, calling preventDefault to suppress the + * history-nav gesture. Vertical-dominant wheel events are left untouched so + * normal page scroll still works. Chrome/Firefox are covered by the + * `overscrollBehaviorX: 'contain'` style on the FlatList. + * + * Listener must be non-passive so preventDefault is honored. + */ + const onWheel = (e: WheelEvent) => { + if (!IS_WEB_SAFARI) return + // Only act on predominantly-horizontal scrolls. Vertical-dominant events + // are page scroll and must not be swallowed. + if (Math.abs(e.deltaX) <= Math.abs(e.deltaY)) return + + e.preventDefault() + + // Cancel any in-progress settle tween so manual scrolling feels direct. + if (stopTween) { + stopTween() + stopTween = null + } + if (overscrollX !== 0) clearOverscroll() + + const maxScroll = el.scrollWidth - el.clientWidth + const next = Math.max(0, Math.min(el.scrollLeft + e.deltaX, maxScroll)) + scrollTo(next) + + // Keep the active index in sync so keyboard/lightbox stay correct, but + // only settle when it actually changes - onSettle moves focus, which we + // don't want to thrash on every wheel tick. + let accumulated = 0 + let index = 0 + for (let i = 0; i < imageCount; i++) { + const w = (itemWidthsRef.current.get(i) ?? 0) + ITEM_GAP + if (next < accumulated + w / 2) { + index = i + break + } + accumulated += w + if (i === imageCount - 1) index = i + } + if (index !== localIndex) { + localIndex = index + onSettle(index) + } + } + el.addEventListener('mousedown', onMouseDown) + el.addEventListener('wheel', onWheel, {passive: false}) window.addEventListener('mousemove', onMouseMove) window.addEventListener('mouseup', onMouseUp) return () => { el.removeEventListener('mousedown', onMouseDown) + el.removeEventListener('wheel', onWheel) window.removeEventListener('mousemove', onMouseMove) window.removeEventListener('mouseup', onMouseUp) if (stopTween) stopTween() diff --git a/src/components/images/ImageLayoutGrid.tsx b/src/components/images/ImageLayoutGrid.tsx index 320dba70a5..5b4ba608b2 100644 --- a/src/components/images/ImageLayoutGrid.tsx +++ b/src/components/images/ImageLayoutGrid.tsx @@ -3,8 +3,8 @@ import {type StyleProp, View, type ViewStyle} from 'react-native' import {type AnimatedRef, useAnimatedRef} from 'react-native-reanimated' import {type AppBskyEmbedImages} from '@atproto/api' -import {type Dimensions} from '#/view/com/lightbox/ImageViewing/@types' import {atoms as a, useBreakpoints} from '#/alf' +import {type Dimensions} from '#/components/Lightbox/types' import {PostEmbedViewContext} from '#/components/Post/Embed/types' import {GalleryItem} from './ImageLayoutGridItem' @@ -19,21 +19,28 @@ interface ImageLayoutGridProps { onPressIn?: (index: number) => void style?: StyleProp viewContext?: PostEmbedViewContext + isWithinQuote?: boolean } -export function ImageLayoutGrid({style, ...props}: ImageLayoutGridProps) { +export function ImageLayoutGrid({ + style, + isWithinQuote: isWithinQuoteProp, + ...props +}: ImageLayoutGridProps) { const {gtMobile} = useBreakpoints() - const gap = + const isWithinQuote = + isWithinQuoteProp ?? props.viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia - ? gtMobile - ? a.gap_xs - : a.gap_2xs - : a.gap_xs + const gap = isWithinQuote ? (gtMobile ? a.gap_xs : a.gap_2xs) : a.gap_xs return ( - + ) @@ -49,6 +56,7 @@ interface ImageLayoutGridInnerProps { onLongPress?: (index: number) => void onPressIn?: (index: number) => void viewContext?: PostEmbedViewContext + isWithinQuote?: boolean gap: {gap: number} } diff --git a/src/components/images/ImageLayoutGridItem.tsx b/src/components/images/ImageLayoutGridItem.tsx index b809a783fa..640ecd8ba2 100644 --- a/src/components/images/ImageLayoutGridItem.tsx +++ b/src/components/images/ImageLayoutGridItem.tsx @@ -9,8 +9,9 @@ import {Trans} from '@lingui/react/macro' import {type Dimensions} from '#/lib/media/types' import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge' -import {atoms as a, useTheme} from '#/alf' +import {atoms as a, tokens, useTheme} from '#/alf' import {MediaInsetBorder} from '#/components/MediaInsetBorder' +import {ImageContextMenu} from '#/components/Post/Embed/ImageContextMenu' import {PostEmbedViewContext} from '#/components/Post/Embed/types' import {Text} from '#/components/Typography' @@ -28,6 +29,7 @@ interface Props { onPressIn?: EventFunction imageStyle?: StyleProp viewContext?: PostEmbedViewContext + isWithinQuote?: boolean insetBorderStyle?: StyleProp containerRefs: AnimatedRef[] thumbDimsRef: React.RefObject<(Dimensions | null)[]> @@ -41,6 +43,7 @@ export function GalleryItem({ onPressIn, onLongPress, viewContext, + isWithinQuote, insetBorderStyle, containerRefs, thumbDimsRef, @@ -51,47 +54,66 @@ export function GalleryItem({ const image = images[index] const hasAlt = !!image.alt const hideBadges = + isWithinQuote ?? viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia + + const aspect = + image.aspectRatio && image.aspectRatio.height > 0 + ? image.aspectRatio.width / image.aspectRatio.height + : undefined + + // The tap handler and the peek-commit handler do the same thing: open the + // lightbox with this cell's ref + dims so the lightbox's return animation + // can target the original thumbnail. + const openLightboxAtIndex = onPress + ? () => onPress(index, containerRefs, thumbDimsRef.current.slice()) + : undefined + return ( - onPress(index, containerRefs, thumbDimsRef.current.slice()) - : undefined - } - onPressIn={onPressIn ? () => onPressIn(index) : undefined} - onLongPress={onLongPress ? () => onLongPress(index) : undefined} - android_ripple={{ - color: utils.alpha(t.atoms.bg.backgroundColor, 0.2), - foreground: true, - }} - style={[ - a.flex_1, - a.overflow_hidden, - t.atoms.bg_contrast_25, - imageStyle, - ]} - accessibilityRole="button" - accessibilityLabel={image.alt || _(msg`Image`)} - accessibilityHint=""> - { - thumbDimsRef.current[index] = { - width: e.source.width, - height: e.source.height, - } + + onPressIn(index) : undefined} + onLongPress={onLongPress ? () => onLongPress(index) : undefined} + android_ripple={{ + color: utils.alpha(t.atoms.bg.backgroundColor, 0.2), + foreground: true, }} - loading="lazy" - /> - - + style={[ + a.flex_1, + a.overflow_hidden, + t.atoms.bg_contrast_25, + imageStyle, + ]} + accessibilityRole="button" + accessibilityLabel={image.alt || _(msg`Image`)} + accessibilityHint=""> + { + thumbDimsRef.current[index] = { + width: e.source.width, + height: e.source.height, + } + }} + loading="lazy" + useAppleWebpCodec + /> + + + {hasAlt && !hideBadges ? ( + + + + ) +} + +function GroupChatJoinDialogInner({code}: {code?: string}) { + const {t: l} = useLingui() + + return ( + + + + + + + ) +} + +function GroupChatJoinDialogContent({code}: {code?: string}) { + const t = useTheme() + const {t: l} = useLingui() + const {groupChatJoinDialogControl: control} = useIntentDialogs() + const {hasSession} = useSession() + const moderationOpts = useModerationOpts() + const navigation = useNavigation() + const queryClient = useQueryClient() + const ax = useAnalytics() + + const logViewOnce = useCallOnce() + useEffect(() => { + logViewOnce(() => { + ax.metric('groupchat:landingPage:view', {hasSession}) + }) + }, [ax, hasSession, logViewOnce]) + + const {data, error, isLoading} = useJoinLinkPreviewsQuery({ + codes: code ? [code] : undefined, + hasSession, + staleTime: 0, + }) + + const {mutate: joinGroupChat, isPending: isJoinPending} = + useRequestJoinGroupChat({ + onSuccess: data => { + switch (data.status) { + case 'pending': + // Optimistically mark the link as requested so any invite cards + // backed by the preview cache (e.g. the DM embed) flip to + // "Requested" right away, rather than waiting on a server refetch + // that can lag behind the write. + if (code) + setJoinLinkPreviewRequestedForCode(queryClient, code, true) + ax.metric('groupchat:inviteLink:redeem', {}) + control.close(() => { + Toast.show( + l`Access requested! The group owner will review your request.`, + ) + }) + break + case 'joined': { + if (data.convo && data.convo.id) { + // Refetch so invite cards pick up the resolved convo and switch + // their action to "Open chat". + if (code) + void invalidateJoinLinkPreviewsForCode(queryClient, code) + ax.metric('groupchat:inviteLink:redeem', {}) + control.close(() => { + Toast.show(l`Successfully joined the group chat!`) + navigation.navigate('MessagesConversation', { + conversation: data.convo!.id, + }) + }) + } else { + logger.warn('Request to join group chat returned no convo ID', { + status: data.status, + convoId: data.convo?.id, + }) + } + break + } + } + }, + onError: error => { + let errorMessage = l`Failed to join the group chat. Please try again.` + if (isNetworkError(error)) { + errorMessage = l`There was a problem with your internet connection, please try again` + } else if (error instanceof ChatBskyGroupRequestJoin.ConvoLockedError) { + errorMessage = l`This conversation is locked.` + } else if ( + error instanceof ChatBskyGroupRequestJoin.FollowRequiredError + ) { + errorMessage = l`Only followers can join this group chat.` + } else if (error instanceof ChatBskyGroupRequestJoin.InvalidCodeError) { + errorMessage = l`Invalid group chat code.` + } else if ( + error instanceof ChatBskyGroupRequestJoin.LinkDisabledError + ) { + errorMessage = l`This invite link has been disabled.` + } else if ( + error instanceof ChatBskyGroupRequestJoin.MemberLimitReachedError + ) { + errorMessage = l`The member limit has been reached.` + const preview = data?.joinLinkPreviews[0] + if ( + ChatBskyGroupDefs.isJoinLinkPreviewView(preview) && + preview.convo?.id + ) { + ax.metric('groupchat:join:memberLimitReached', { + convoId: preview.convo.id, + }) + } + } else if (error instanceof ChatBskyGroupRequestJoin.UserKickedError) { + errorMessage = l`You have been previously removed from this group and can’t join it using this link.` + } + Toast.show(errorMessage) + }, + }) + + const {mutate: withdrawRequest, isPending: isWithdrawPending} = + useWithdrawJoinGroupChatRequest({ + onSuccess: () => { + // Optimistically clear the requested state so invite cards backed by + // the preview cache flip back to "Request to join" right away. + if (code) setJoinLinkPreviewRequestedForCode(queryClient, code, false) + control.close(() => { + Toast.show(l`Join request rescinded.`) + }) + }, + onError: error => { + let errorMessage = l`Failed to rescind your request. Please try again.` + if (isNetworkError(error)) { + errorMessage = l`There was a problem with your internet connection, please try again` + } else if ( + error instanceof + ChatBskyGroupWithdrawJoinRequest.InvalidJoinRequestError + ) { + errorMessage = l`Invalid rescind request.` + } + Toast.show(errorMessage) + }, + }) + + const { + state: interacted, + onIn: onInteract, + onOut: onInteractOut, + } = useInteractionState() + + const handleJoin = () => { + if (!code) return + joinGroupChat({code}) + } + + const handleWithdraw = () => { + if (!convoId) return + withdrawRequest({convoId}) + } + + // Fallback if the prefetch exceeds the timeout + if (isLoading || !data || !moderationOpts) { + return ( + + + + ) + } + + if (error) { + return ( + <> + + + This invite link is invalid + + + + ) + } + + const joinLinkPreview = data.joinLinkPreviews[0] + + if (!ChatBskyGroupDefs.isJoinLinkPreviewView(joinLinkPreview)) { + return ( + <> + + + + + Chat invite link no longer available + + + + + + ) + } + + const convoId = joinLinkPreview.convoId + const isFollowing = joinLinkPreview.owner.viewer?.followedBy ?? false + const hasRequested = + !joinLinkPreview.convo && joinLinkPreview.viewer?.requestedAt != null + + let canJoin = true + let ButtonIconImage = isJoinPending || isWithdrawPending ? Loader : JoinIcon + let buttonText = joinLinkPreview.requireApproval + ? l`Request to join` + : l`Join` + let buttonColor: ButtonColor = 'primary' + if (joinLinkPreview.memberCount >= joinLinkPreview.memberLimit) { + canJoin = false + ButtonIconImage = HandIcon + buttonText = l`This chat is full` + buttonColor = 'secondary' + } else if (joinLinkPreview.joinRule === 'followedByOwner' && !isFollowing) { + canJoin = false + ButtonIconImage = HandIcon + buttonText = l`Only people the chat owner follows can join` + buttonColor = 'secondary' + } else if (hasRequested) { + ButtonIconImage = XIcon + buttonText = l`Rescind request` + buttonColor = 'secondary' + } + + return ( + <> + + + + + + Group chat + + + {joinLinkPreview.name} + + + + + + {joinLinkPreview.memberCount}/{joinLinkPreview.memberLimit}{' '} + + + + + + + + {joinLinkPreview.joinRule === 'followedByOwner' + ? l`Followers can join` + : l`Anyone can join`} + + + + + + By{' '} + { + onInteract() + }, + onMouseLeave: () => { + onInteractOut() + }, + })}> + {createSanitizedDisplayName( + joinLinkPreview.owner, + true, + moderateProfile(joinLinkPreview.owner, moderationOpts).ui( + 'displayName', + ), + )} + + + + + + {sanitizeHandle(joinLinkPreview.owner.handle, '@')} + + + + + {joinLinkPreview.convo ? ( + + ) : ( + + )} + + ) +} diff --git a/src/components/intents/IntentDialogs.tsx b/src/components/intents/IntentDialogs.tsx index ea72f8c099..76809b366a 100644 --- a/src/components/intents/IntentDialogs.tsx +++ b/src/components/intents/IntentDialogs.tsx @@ -1,13 +1,30 @@ -import {createContext, useContext, useMemo, useState} from 'react' +import { + createContext, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from 'react' +import {usePrefetchJoinLinkPreviews} from '#/state/queries/join-links' +import {useSession} from '#/state/session' +import { + useActiveGroupChatJoinRequest, + useSetActiveLanding, +} from '#/state/shell/landing' import * as Dialog from '#/components/Dialog' import {type DialogControlProps} from '#/components/Dialog' +import {GroupChatJoinDialog} from '#/components/intents/GroupChatJoinDialog' import {VerifyEmailIntentDialog} from '#/components/intents/VerifyEmailIntentDialog' interface Context { verifyEmailDialogControl: DialogControlProps verifyEmailState: {code: string} | undefined setVerifyEmailState: (state: {code: string} | undefined) => void + groupChatJoinDialogControl: DialogControlProps + groupChatJoinState: {code: string} | undefined + setGroupChatJoinState: (state: {code: string} | undefined) => void } const Context = createContext({} as Context) @@ -19,20 +36,70 @@ export function Provider({children}: {children: React.ReactNode}) { const [verifyEmailState, setVerifyEmailState] = useState< {code: string} | undefined >() + const groupChatJoinDialogControl = Dialog.useDialogControl() + const [groupChatJoinState, setGroupChatJoinState] = useState< + {code: string} | undefined + >() + + const {hasSession} = useSession() + const groupChatLanding = useActiveGroupChatJoinRequest() + const setActiveLanding = useSetActiveLanding() + const prefetchJoinLinkPreviews = usePrefetchJoinLinkPreviews() + const landingHandledRef = useRef(false) + + useEffect(() => { + if (hasSession && groupChatLanding && !landingHandledRef.current) { + landingHandledRef.current = true + const code = groupChatLanding.code + setActiveLanding(undefined) + const prefetch = prefetchJoinLinkPreviews({ + codes: [code], + hasSession: true, + }) + void Promise.race([ + prefetch, + new Promise(res => setTimeout(res, 200)), + ]).finally(() => { + setGroupChatJoinState({code}) + groupChatJoinDialogControl.open() + }) + } + if (!groupChatLanding) { + landingHandledRef.current = false + } + }, [ + hasSession, + groupChatLanding, + setActiveLanding, + setGroupChatJoinState, + prefetchJoinLinkPreviews, + groupChatJoinDialogControl, + ]) const value = useMemo( () => ({ verifyEmailDialogControl, verifyEmailState, setVerifyEmailState, + groupChatJoinDialogControl, + groupChatJoinState, + setGroupChatJoinState, }), - [verifyEmailDialogControl, verifyEmailState, setVerifyEmailState], + [ + verifyEmailDialogControl, + verifyEmailState, + setVerifyEmailState, + groupChatJoinDialogControl, + groupChatJoinState, + setGroupChatJoinState, + ], ) return ( {children} + ) } diff --git a/src/components/moderation/BlockDialog.tsx b/src/components/moderation/BlockDialog.tsx new file mode 100644 index 0000000000..540f1603f0 --- /dev/null +++ b/src/components/moderation/BlockDialog.tsx @@ -0,0 +1,398 @@ +import {useState} from 'react' +import {View} from 'react-native' +import { + type ChatBskyConvoDefs, + ChatBskyConvoLeaveConvo, + ChatBskyGroupRemoveMembers, +} from '@atproto/api' +import {Trans, useLingui} from '@lingui/react/macro' +import {useQueryClient} from '@tanstack/react-query' + +import {isNetworkError} from '#/lib/strings/errors' +import {logger} from '#/logger' +import {type Shadow} from '#/state/cache/types' +import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' +import { + createListMutualGroupsQueryKey, + useListMutualGroupsQuery, +} from '#/state/queries/messages/list-mutual-groups' +import {useRemoveFromGroupChat} from '#/state/queries/messages/remove-from-group' +import {useSession} from '#/state/session' +import {atoms as a, native, useTheme, web} from '#/alf' +import {AvatarBubbles} from '#/components/AvatarBubbles' +import {Button, ButtonIcon, ButtonText} from '#/components/Button' +import * as Dialog from '#/components/Dialog' +import {type DialogControlProps} from '#/components/Dialog' +import {parseConvoView} from '#/components/dms/util' +import {Loader} from '#/components/Loader' +import * as Toast from '#/components/Toast' +import {Text} from '#/components/Typography' +import {type AnyProfileView} from '#/types/bsky/profile' + +type Item = ChatBskyConvoDefs.ConvoView + +type BlockDialogProps = { + control: DialogControlProps + profile: Shadow + onBlock: () => Promise + currentConvoId?: string +} + +export function BlockDialog({ + control, + profile, + onBlock, + currentConvoId, +}: BlockDialogProps) { + return ( + + + + + + + + ) +} + +function BlockDialogInner({ + control, + profile, + onBlock, + currentConvoId, +}: { + control: DialogControlProps + profile: Shadow + onBlock: () => Promise + currentConvoId?: string +}) { + const t = useTheme() + const {t: l} = useLingui() + + const [headerHeight, setHeaderHeight] = useState(0) + const [footerHeight, setFooterHeight] = useState(0) + + /* + * Optimistically hide convos the viewer has left or removed the profile + * from, before the query refetches. We don't expect many items here, so a + * simple filter is fine. + */ + const [removedConvoIds, setRemovedConvoIds] = useState>( + () => new Set(), + ) + const onOptimisticallyRemoveConvo = (convoId: string) => { + setRemovedConvoIds(prev => { + const next = new Set(prev) + next.add(convoId) + return next + }) + } + const onRestoreConvo = (convoId: string) => { + setRemovedConvoIds(prev => { + const next = new Set(prev) + next.delete(convoId) + return next + }) + } + + const {data, isLoading, hasNextPage, isFetchingNextPage, fetchNextPage} = + useListMutualGroupsQuery({ + subject: profile.did, + enabled: !profile.viewer?.blocking, + }) + const items: Item[] = (data?.pages.flatMap(page => page.convos) ?? []).filter( + item => !removedConvoIds.has(item.id), + ) + const hasMutualGroupChats = items.length > 0 + + const onEndReached = async () => { + if (isFetchingNextPage || !hasNextPage) return + try { + await fetchNextPage() + } catch (err) { + logger.error('Failed to load more mutual group chats', {message: err}) + } + } + + const renderItems = ({item}: {item: Item}) => { + return ( + + ) + } + + const listHeader = ( + setHeaderHeight(evt.nativeEvent.layout.height)}> + + + {profile.viewer?.blocking ? ( + Unblock account? + ) : ( + Block account? + )} + + + {profile.viewer?.blocking ? ( + + The account will be able to interact with you after unblocking. + + ) : profile.associated?.labeler ? ( + + Blocking will not prevent labels from being applied on your + account, but it will stop this account from replying in your + threads or interacting with you. + + ) : ( + + Blocked accounts cannot reply in your threads, mention you, or + otherwise interact with you. + + )} + + + {hasMutualGroupChats ? ( + + + Mutual group chats + + + ) : null} + + ) + + const footer = ( + + + + + ) + + if (isLoading || !hasMutualGroupChats) { + return ( + + {listHeader} + {isLoading ? ( + + + + ) : null} + {footer} + + ) + } + + return ( + + + + ) : null + } + footer={ + setFooterHeight(evt.nativeEvent.layout.height)}> + {footer} + + } + contentContainerStyle={[a.gap_0, {paddingBottom: footerHeight}]} + scrollIndicatorInsets={{top: headerHeight, bottom: footerHeight}} + onEndReached={() => void onEndReached()} + onEndReachedThreshold={0.5} + style={[web([{height: '100vh', maxHeight: 600}])]} + webInnerStyle={[{maxWidth: 420}]} + /> + ) +} + +function MutualGroupChat({ + view, + profileDid, + currentConvoId, + onOptimisticallyRemoveConvo, + onRestoreConvo, +}: { + view: ChatBskyConvoDefs.ConvoView + profileDid: string + currentConvoId?: string + onOptimisticallyRemoveConvo: (convoId: string) => void + onRestoreConvo: (convoId: string) => void +}) { + const t = useTheme() + const {t: l} = useLingui() + const {currentAccount} = useSession() + const queryClient = useQueryClient() + + const convo = parseConvoView(view, currentAccount?.did) + + const {mutate: leaveConvo, isPending: isLeavePending} = useLeaveConvo( + convo?.view.id, + { + onSuccess: () => { + Toast.show(l`Left group chat.`) + void queryClient.invalidateQueries({ + queryKey: createListMutualGroupsQueryKey({subject: profileDid}), + }) + }, + onError: error => { + onRestoreConvo(view.id) + logger.error('Error leaving group chat', {message: error}) + let errorMessage = l`Could not leave chat.` + if (isNetworkError(error)) { + errorMessage = l`A network error occurred. Please check your internet connection.` + } else if (error instanceof ChatBskyConvoLeaveConvo.InvalidConvoError) { + errorMessage = l`Chat not found.` + } else if ( + error instanceof ChatBskyConvoLeaveConvo.OwnerCannotLeaveError + ) { + errorMessage = l`Chat owners cannot leave a group chat.` + } + Toast.show(errorMessage, {type: 'error'}) + }, + }, + ) + + const {mutate: removeMembers, isPending: isRemovePending} = + useRemoveFromGroupChat(convo?.view.id, { + onSuccess: () => { + Toast.show(l`Member removed from group chat.`) + void queryClient.invalidateQueries({ + queryKey: createListMutualGroupsQueryKey({subject: profileDid}), + }) + }, + onError: error => { + onRestoreConvo(view.id) + logger.error('Error removing group chat member', {message: error}) + let errorMessage = l`Could not remove member.` + if (isNetworkError(error)) { + errorMessage = l`A network error occurred. Please check your internet connection.` + } else if ( + error instanceof ChatBskyGroupRemoveMembers.InvalidConvoError + ) { + errorMessage = l`Chat not found.` + } else if ( + error instanceof ChatBskyGroupRemoveMembers.InsufficientRoleError + ) { + errorMessage = l`You must be a chat owner to remove a member.` + } + Toast.show(errorMessage, {type: 'error'}) + }, + }) + + if (!convo || convo.kind !== 'group') return null + + const owner = convo.primaryMember + const isViewerOwner = owner?.did != null && owner.did === currentAccount?.did + const isProfileOwner = owner?.did != null && owner.did === profileDid + const isCurrentConvo = view.id === currentConvoId + + return ( + + + + + + {convo.details.name} + + {isViewerOwner ? ( + + You own this chat + + ) : isProfileOwner ? ( + + They own this chat + + ) : null} + + + {isViewerOwner ? ( + + ) : isCurrentConvo ? ( + + Current chat + + ) : ( + + )} + + ) +} diff --git a/src/components/moderation/ContentHider.tsx b/src/components/moderation/ContentHider.tsx index b05be66290..b603c738d4 100644 --- a/src/components/moderation/ContentHider.tsx +++ b/src/components/moderation/ContentHider.tsx @@ -139,7 +139,7 @@ function ContentHiderActive({ return [...new Set(selfBlurNames)].join(', ') }, [ _, - modui?.blurs, + modui.blurs, blur, desc.name, desc.isSubjectAccount, diff --git a/src/components/moderation/ModerationDetailsDialog.tsx b/src/components/moderation/ModerationDetailsDialog.tsx index 9fbce2f51e..ddc42effa3 100644 --- a/src/components/moderation/ModerationDetailsDialog.tsx +++ b/src/components/moderation/ModerationDetailsDialog.tsx @@ -9,7 +9,8 @@ import {useModerationCauseDescription} from '#/lib/moderation/useModerationCause import {makeProfileLink} from '#/lib/routes/links' import {listUriToHref} from '#/lib/strings/url-helpers' import {useSession} from '#/state/session' -import {atoms as a, useGutters, useTheme} from '#/alf' +import {atoms as a, useGutters, useTheme, web} from '#/alf' +import {Admonition} from '#/components/Admonition' import * as Dialog from '#/components/Dialog' import {InlineLinkText} from '#/components/Link' import {type AppModerationCause} from '#/components/Pills' @@ -143,7 +144,10 @@ function ModerationDetailsDialogInner({ paddingLeft: 0, paddingRight: 0, paddingBottom: 0, - }}> + }} + style={web({ + maxWidth: 460, + })}> {name} @@ -151,6 +155,15 @@ function ModerationDetailsDialogInner({ {description} + + {desc.isSubjectAccount && ( + + + This moderation was applied to the entire user account and will + appear on all posts. + + + )} {modcause?.type === 'label' && ( diff --git a/src/components/moderation/ReportDialog/action.ts b/src/components/moderation/ReportDialog/action.ts index ee776fd623..ac702abd67 100644 --- a/src/components/moderation/ReportDialog/action.ts +++ b/src/components/moderation/ReportDialog/action.ts @@ -55,7 +55,9 @@ export function useSubmitReportMutation() { let report: | ComAtprotoModerationCreateReport.InputSchema | (Omit & { - subject: $Typed + subject: + | $Typed + | $Typed }) switch (subject.type) { @@ -99,6 +101,18 @@ export function useSubmitReportMutation() { } break } + case 'convo': { + report = { + reasonType, + reason: state.details, + subject: { + $type: 'chat.bsky.convo.defs#convoRef', + convoId: subject.convoId, + did: subject.did, + }, + } + break + } } if (__DEV__) { diff --git a/src/components/moderation/ReportDialog/const.ts b/src/components/moderation/ReportDialog/const.ts index 7e5075cce2..b516ec0006 100644 --- a/src/components/moderation/ReportDialog/const.ts +++ b/src/components/moderation/ReportDialog/const.ts @@ -120,4 +120,4 @@ export const BSKY_LABELER_ONLY_REPORT_REASONS: Set = * moderation service. */ export const BSKY_LABELER_ONLY_SUBJECT_TYPES: Set = - new Set(['convoMessage', 'status']) + new Set(['convoMessage', 'convo', 'status']) diff --git a/src/components/moderation/ReportDialog/copy.ts b/src/components/moderation/ReportDialog/copy.ts index cce0cb5665..f682bc59af 100644 --- a/src/components/moderation/ReportDialog/copy.ts +++ b/src/components/moderation/ReportDialog/copy.ts @@ -60,6 +60,12 @@ export function useCopyForSubject(subject: ParsedReportSubject) { } } } + case 'convo': { + return { + title: _(msg`Report this conversation`), + subtitle: _(msg`Why should this conversation be reviewed?`), + } + } } }, [_, subject]) } diff --git a/src/components/moderation/ReportDialog/index.tsx b/src/components/moderation/ReportDialog/index.tsx index 678fd8886a..d1d3aa5584 100644 --- a/src/components/moderation/ReportDialog/index.tsx +++ b/src/components/moderation/ReportDialog/index.tsx @@ -8,9 +8,7 @@ import { } from 'react' import {Pressable, type ScrollView, View} from 'react-native' import {type AppBskyLabelerDefs, BSKY_LABELER_DID} from '@atproto/api' -import {msg} from '@lingui/core/macro' -import {useLingui} from '@lingui/react' -import {Trans} from '@lingui/react/macro' +import {Trans, useLingui} from '@lingui/react/macro' import {wait} from '#/lib/async/wait' import {getLabelingServiceTitle} from '#/lib/moderation' @@ -77,9 +75,11 @@ export function ReportDialog( () => (props.subject ? parseReportSubject(props.subject) : undefined), [props.subject], ) + const propsOnClose = props.onClose const onClose = useCallback(() => { ax.metric('reportDialog:close', {}) - }, [ax]) + propsOnClose?.() + }, [ax, propsOnClose]) return ( @@ -93,9 +93,9 @@ export function ReportDialog( * developer, but nevertheless we should have a graceful fallback. */ function Invalid() { - const {_} = useLingui() + const {t: l} = useLingui() return ( - + Invalid report subject @@ -114,7 +114,7 @@ function Inner(props: ReportDialogProps) { const ax = useAnalytics() const logger = ax.logger.useChild(ax.logger.Context.ReportDialog) const t = useTheme() - const {_} = useLingui() + const {t: l} = useLingui() const ref = useRef(null) const { data: allLabelers, @@ -131,8 +131,8 @@ function Inner(props: ReportDialogProps) { * Submission handling */ const {mutateAsync: submitReport} = useSubmitReportMutation() - const [isPending, setPending] = useState(false) - const [isSuccess, setSuccess] = useState(false) + const [isPending, setIsPending] = useState(false) + const [isSuccess, setIsSuccess] = useState(false) // some reasons ONLY go to Bluesky const isBskyOnlyReason = state?.selectedOption?.reason @@ -154,7 +154,10 @@ function Inner(props: ReportDialogProps) { if (subjectTypes === undefined) return true if (props.subject.type === 'account') { return subjectTypes.includes('account') - } else if (props.subject.type === 'convoMessage') { + } else if ( + props.subject.type === 'convoMessage' || + props.subject.type === 'convo' + ) { return subjectTypes.includes('chat') } else { return subjectTypes.includes('record') @@ -164,7 +167,11 @@ function Inner(props: ReportDialogProps) { const collections: string[] | undefined = l.subjectCollections if (collections === undefined) return true // all chat collections accepted, since only Bluesky handles chats - if (props.subject.type === 'convoMessage') return true + if ( + props.subject.type === 'convoMessage' || + props.subject.type === 'convo' + ) + return true return collections.includes(props.subject.nsid) }) .filter(l => { @@ -208,7 +215,7 @@ function Inner(props: ReportDialogProps) { logger.info('submitting') try { - setPending(true) + setIsPending(true) // wait at least 1s, make it feel substantial await wait( 1e3, @@ -217,7 +224,7 @@ function Inner(props: ReportDialogProps) { state, }), ) - setSuccess(true) + setIsSuccess(true) ax.metric('reportDialog:success', { reason: state.selectedOption?.reason ?? '', labeler: state.selectedLabeler?.creator.handle ?? '', @@ -229,29 +236,20 @@ function Inner(props: ReportDialogProps) { props.onAfterSubmit?.() }) }, 1e3) - } catch (e: any) { + } catch (err) { + const e = err as Error ax.metric('reportDialog:failure', {}) logger.error(e, { source: 'ReportDialog', }) dispatch({ type: 'setError', - error: _(msg`Something went wrong. Please try again.`), + error: l`Something went wrong. Please try again.`, }) } finally { - setPending(false) + setIsPending(false) } - }, [ - _, - submitReport, - state, - dispatch, - props.subject, - props.control, - props.onAfterSubmit, - setPending, - setSuccess, - ]) + }, [logger, submitReport, props, state, ax, l]) useCallOnce(() => { ax.metric('reportDialog:open', { @@ -262,7 +260,7 @@ function Inner(props: ReportDialogProps) { return ( @@ -293,8 +291,8 @@ function Inner(props: ReportDialogProps) { refetchLabelers()}> + label={l`Retry loading report options`} + onPress={() => void refetchLabelers()}> Retry @@ -311,7 +309,7 @@ function Inner(props: ReportDialogProps) { + ) + })} + + ) +} diff --git a/src/features/gifPicker/components/GifPickerErrorBoundary.tsx b/src/features/gifPicker/components/GifPickerErrorBoundary.tsx new file mode 100644 index 0000000000..a57ad46551 --- /dev/null +++ b/src/features/gifPicker/components/GifPickerErrorBoundary.tsx @@ -0,0 +1,52 @@ +import {Trans, useLingui} from '@lingui/react/macro' + +import {ErrorScreen} from '#/view/com/util/error/ErrorScreen' +import {atoms as a} from '#/alf' +import {Button, ButtonText} from '#/components/Button' +import * as Dialog from '#/components/Dialog' + +export function GifPickerErrorBoundary({details}: {details?: string}) { + const {t: l} = useLingui() + const control = Dialog.useDialogContext() + + return ( + + + + + + ) +} diff --git a/src/features/gifPicker/components/GifPickerGrid.tsx b/src/features/gifPicker/components/GifPickerGrid.tsx new file mode 100644 index 0000000000..a34aeacab0 --- /dev/null +++ b/src/features/gifPicker/components/GifPickerGrid.tsx @@ -0,0 +1,122 @@ +import {forwardRef} from 'react' +import {Platform, useWindowDimensions, View} from 'react-native' + +import {cleanError} from '#/lib/strings/errors' +import {type ListMethods} from '#/view/com/util/List' +import {atoms as a, native, useBreakpoints, web} from '#/alf' +import * as Dialog from '#/components/Dialog' +import {ListFooter} from '#/components/Lists' +import {GifPickerItem} from '#/features/gifPicker/components/GifPickerItem' +import {type Gif} from '#/features/gifPicker/types' + +type Props = { + items: Gif[] + header: React.ReactNode + hasData: boolean + isFetchingNextPage: boolean + error: unknown + fetchNextPage: () => Promise + onEndReached: () => void + onSelectGif: (gif: Gif) => void +} + +export const GifPickerGrid = forwardRef( + function GifPickerGrid( + { + items, + header, + hasData, + isFetchingNextPage, + error, + fetchNextPage, + onEndReached, + onSelectGif, + }, + ref, + ) { + const {gtMobile} = useBreakpoints() + const {height} = useWindowDimensions() + const numColumns = gtMobile ? 3 : 2 + + const columns = distributeIntoColumns(items, numColumns) + + /** + * The grid is a single FlatList row because the tiles are distributed + * into columns up front for masonry. `onEndReached` still fires against + * the outer FlatList's scroll position, so pagination behaves the same + * as a conventional grid. + */ + const data = hasData ? [columns] : [] + + return ( + ( + + {item.map((column, i) => ( + + {column.map(gif => ( + + ))} + + ))} + + )} + keyExtractor={(_item, index) => `masonry-${index}`} + contentContainerStyle={[native([a.px_xl, {minHeight: height}])]} + webInnerStyle={[web({minHeight: '80vh'})]} + webInnerContentContainerStyle={[web(a.pb_0)]} + ListHeaderComponent={<>{header}} + stickyHeaderIndices={[0]} + onEndReached={onEndReached} + onEndReachedThreshold={1} + // On web, "on-drag" blurs the focused input on ANY scroll event, + // including programmatic scrolls (e.g., content shrinking when search + // results swap in). That breaks search-while-scrolled — the blur fires + // mid-typing and subsequent keystrokes go nowhere. + keyboardDismissMode={Platform.OS === 'web' ? 'none' : 'on-drag'} + ListFooterComponent={ + hasData ? ( + + ) : null + } + /> + ) + }, +) + +/** + * Walks `items` in order and pushes each one into the currently shortest + * column, tracking accumulated height-per-unit-width from each GIF's + * intrinsic aspect ratio. Preserves ordering top-to-bottom within each + * column, which keeps pagination behavior intuitive as new pages stream in. + */ +function distributeIntoColumns(items: Gif[], numColumns: number): Gif[][] { + const columns: Gif[][] = Array.from({length: numColumns}, () => []) + const heights = new Array(numColumns).fill(0) + + for (const item of items) { + const [w, h] = item.media_formats.tinygif.dims + const ratio = w > 0 && h > 0 ? h / w : 1 + + let shortest = 0 + for (let i = 1; i < numColumns; i++) { + if (heights[i] < heights[shortest]) shortest = i + } + columns[shortest].push(item) + heights[shortest] += ratio + } + + return columns +} diff --git a/src/features/gifPicker/components/GifPickerHeader.tsx b/src/features/gifPicker/components/GifPickerHeader.tsx new file mode 100644 index 0000000000..ee50cdb8e4 --- /dev/null +++ b/src/features/gifPicker/components/GifPickerHeader.tsx @@ -0,0 +1,78 @@ +import {type Ref} from 'react' +import {type TextInput, View} from 'react-native' +import {useLingui} from '@lingui/react/macro' + +import {atoms as a, native, useTheme} from '#/alf' +import {Button, ButtonIcon} from '#/components/Button' +import * as TextField from '#/components/forms/TextField' +import {MagnifyingGlass_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass' +import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' + +export function GifPickerHeader({ + inputRef, + onChangeText, + onClear, + onEscape, + canClear, +}: { + inputRef: Ref + onChangeText: (text: string) => void + onClear: () => void + onEscape: () => void + canClear: boolean +}) { + const {t: l} = useLingui() + const t = useTheme() + + return ( + + + + { + if (nativeEvent.key === 'Escape') { + onEscape() + } + }} + /> + {canClear && ( + + )} + + + ) +} diff --git a/src/features/gifPicker/components/GifPickerItem.tsx b/src/features/gifPicker/components/GifPickerItem.tsx new file mode 100644 index 0000000000..e307e064a3 --- /dev/null +++ b/src/features/gifPicker/components/GifPickerItem.tsx @@ -0,0 +1,60 @@ +import {Image} from 'expo-image' +import {useLingui} from '@lingui/react/macro' + +import {atoms as a, useTheme} from '#/alf' +import {Button} from '#/components/Button' +import {useAnalytics} from '#/analytics' +import {type Gif} from '#/features/gifPicker/types' +import {gifPreviewUrl} from '#/features/gifPicker/utils' + +export function GifPickerItem({ + gif, + onSelectGif, +}: { + gif: Gif + onSelectGif: (gif: Gif) => void +}) { + const ax = useAnalytics() + const {t: l} = useLingui() + const t = useTheme() + + const [width, height] = gif.media_formats.tinygif.dims + const aspectRatio = width > 0 && height > 0 ? width / height : 1 + + const onPress = () => { + ax.metric('composer:gif:select', {}) + onSelectGif(gif) + } + + return ( + + ) +} diff --git a/src/features/gifPicker/components/GifPickerPlaceholder.tsx b/src/features/gifPicker/components/GifPickerPlaceholder.tsx new file mode 100644 index 0000000000..d498a8285e --- /dev/null +++ b/src/features/gifPicker/components/GifPickerPlaceholder.tsx @@ -0,0 +1,65 @@ +import {useLingui} from '@lingui/react/macro' + +import {ListMaybePlaceholder} from '#/components/Lists' + +export function GifPickerPlaceholder({ + isLoading, + isError, + isSearching, + isRecentsEmpty, + query, + onRetry, + onGoBack, +}: { + isLoading: boolean + isError: boolean + isSearching: boolean + isRecentsEmpty: boolean + query: string + onRetry: () => Promise + onGoBack: () => void +}) { + const {t: l} = useLingui() + + const emptyMessage = isSearching + ? l({ + message: `No GIFs found for "${query}".`, + comment: + 'Empty-state message shown in the GIF picker when a search returns zero results. Placeholder is the user’s search query.', + }) + : isRecentsEmpty + ? l({ + message: 'No recent GIFs yet. Pick one to see it here.', + comment: + 'Empty-state message shown in the GIF picker’s Recents tab before the user has selected any GIFs.', + }) + : l({ + message: 'No GIFs to show right now. Try again in a moment.', + comment: + 'Empty-state message shown when the trending/featured GIF feed returns no results (rare, usually a transient provider issue).', + }) + + return ( + + ) +} diff --git a/src/features/gifPicker/hooks/useGifPickerData.ts b/src/features/gifPicker/hooks/useGifPickerData.ts new file mode 100644 index 0000000000..f598b5b719 --- /dev/null +++ b/src/features/gifPicker/hooks/useGifPickerData.ts @@ -0,0 +1,25 @@ +import { + useFeaturedGifsQuery, + useGifSearchQuery, +} from '#/features/gifPicker/queries' + +/** + * Single entry point for the GIF picker's data layer. Routes between the + * featured and search endpoints so the UI only ever consumes one query result. + */ +export function useGifPickerData( + query: string, + {enabled = true}: {enabled?: boolean} = {}, +) { + const isSearching = query.length > 0 + + const featured = useFeaturedGifsQuery({enabled: enabled && !isSearching}) + const search = useGifSearchQuery(query, {enabled: enabled && isSearching}) + + const active = isSearching ? search : featured + + return { + ...active, + isSearching, + } +} diff --git a/src/features/gifPicker/hooks/useRecentGifs.ts b/src/features/gifPicker/hooks/useRecentGifs.ts new file mode 100644 index 0000000000..a2230005bd --- /dev/null +++ b/src/features/gifPicker/hooks/useRecentGifs.ts @@ -0,0 +1,40 @@ +import {useSession} from '#/state/session' +import {type Gif} from '#/features/gifPicker/types' +import {account} from '#/storage' + +const MAX_RECENT_GIFS = 20 + +function readValid(did: string): Gif[] { + const stored = account.get([did, 'recentGifs']) ?? [] + // Earlier builds of this branch stored recents as JSON-serialized strings. + // Drop any malformed entries so a dev with stale local data doesn't crash. + if (stored.some(item => typeof item !== 'object' || item === null)) { + account.remove([did, 'recentGifs']) + return [] + } + return stored +} + +export function useRecentGifs() { + const {currentAccount} = useSession() + const did = currentAccount?.did + + const getRecents = (): Gif[] => { + if (!did) return [] + return readValid(did) + } + + const addRecent = (gif: Gif) => { + if (!did) return + const existing = readValid(did) + const deduped = existing.filter(g => g.id !== gif.id) + const updated = [gif, ...deduped].slice(0, MAX_RECENT_GIFS) + account.set([did, 'recentGifs'], updated) + } + + return { + getRecents, + addRecent, + hasRecents: did ? readValid(did).length > 0 : false, + } +} diff --git a/src/state/queries/klipy.ts b/src/features/gifPicker/queries.ts similarity index 73% rename from src/state/queries/klipy.ts rename to src/features/gifPicker/queries.ts index f87e031e31..759f45f62e 100644 --- a/src/state/queries/klipy.ts +++ b/src/features/gifPicker/queries.ts @@ -1,10 +1,9 @@ import {Platform} from 'react-native' import {getLocales} from 'expo-localization' -import {keepPreviousData, useInfiniteQuery} from '@tanstack/react-query' +import {useInfiniteQuery} from '@tanstack/react-query' import {GIF_KLIPY_FEATURED, GIF_KLIPY_SEARCH} from '#/lib/constants' -import {logger} from '#/logger' -import {type Gif} from '#/state/queries/tenor' +import {type Gif} from '#/features/gifPicker/types' export const RQKEY_ROOT = 'klipy-gif-service' export const RQKEY_FEATURED = [RQKEY_ROOT, 'featured'] @@ -20,6 +19,10 @@ export function useFeaturedGifsQuery(options?: {enabled?: boolean}) { initialPageParam: undefined as string | undefined, getNextPageParam: lastPage => lastPage.next, enabled: options?.enabled, + // Klipy serves time-of-day-sensitive trending; drop cache as soon as the + // picker closes so every reopen issues a fresh request instead of showing + // stale results while a background refetch runs. + gcTime: 0, }) } @@ -33,7 +36,6 @@ export function useGifSearchQuery( initialPageParam: undefined as string | undefined, getNextPageParam: lastPage => lastPage.next, enabled: !!query && options?.enabled !== false, - placeholderData: keepPreviousData, }) } @@ -58,12 +60,12 @@ function createKlipyApi( // 30 is divisible by 2 and 3, so both 2 and 3 column layouts can be used params.set('limit', '30') - params.set('contentfilter', 'high') + params.set('contentfilter', 'low') // PG-13 equivalent const locale = getLocales?.()?.[0] - if (locale) { - params.set('locale', locale.languageTag.replace('-', '_')) + if (locale?.regionCode) { + params.set('locale', locale.regionCode.toLowerCase()) } for (const [key, value] of Object.entries(input)) { @@ -88,21 +90,3 @@ function createKlipyApi( } } } - -/** - * Rewrites a KLIPY static CDN URL through the bsky proxy - * (k.gifs.bsky.app). Mirrors `tenorUrlToBskyGifUrl`, but uses a - * separate hostname from Tenor's t.gifs.bsky.app so the two - * upstreams can be routed independently. - */ -export function klipyUrlToBskyGifUrl(klipyUrl: string) { - let url - try { - url = new URL(klipyUrl) - } catch (e) { - logger.debug('invalid url passed to klipyUrlToBskyGifUrl()') - return '' - } - url.hostname = 'k.gifs.bsky.app' - return url.href -} diff --git a/src/features/gifPicker/types.ts b/src/features/gifPicker/types.ts new file mode 100644 index 0000000000..02c607dc5b --- /dev/null +++ b/src/features/gifPicker/types.ts @@ -0,0 +1,33 @@ +/** + * GIF shape returned by the Bluesky GIF proxy. The field names follow the + * Tenor schema; Klipy responses are normalized to the same shape by the + * proxy so downstream code can be provider-agnostic. + */ +export type Gif = { + created: number + hasaudio: boolean + id: string + media_formats: Record & + Partial> + tags: string[] + title: string + content_description: string + itemurl: string + hascaption: boolean + flags: string + bg_color?: string + url: string +} + +type MediaObject = { + url: string + dims: [number, number] + duration: number + size: number +} + +type BaseContentFormats = 'preview' | 'gif' | 'tinygif' + +type VideoContentFormats = 'mp4' | 'webm' + +export type ContentFormats = BaseContentFormats | VideoContentFormats diff --git a/src/features/gifPicker/utils.ts b/src/features/gifPicker/utils.ts new file mode 100644 index 0000000000..233a0e42e6 --- /dev/null +++ b/src/features/gifPicker/utils.ts @@ -0,0 +1,40 @@ +import {logger} from '#/logger' + +/** + * Rewrites a provider's CDN URL (Tenor or Klipy) to the corresponding + * bsky proxy hostname. Leaves unrecognized hosts untouched. + */ +export function gifPreviewUrl(gifUrl: string) { + try { + const url = new URL(gifUrl) + if (url.hostname === 'media.tenor.com') { + url.hostname = 't.gifs.bsky.app' + return url.href + } + if (url.hostname === 'static.klipy.com') { + url.hostname = 'k.gifs.bsky.app' + return url.href + } + return gifUrl + } catch (e) { + logger.debug('invalid url passed to gifPreviewUrl()') + return '' + } +} + +/** + * Rewrites a KLIPY static CDN URL through the bsky proxy + * (k.gifs.bsky.app) so downstream consumers can route requests + * through Bluesky-owned infrastructure. + */ +export function klipyUrlToBskyGifUrl(klipyUrl: string) { + let url + try { + url = new URL(klipyUrl) + } catch (e) { + logger.debug('invalid url passed to klipyUrlToBskyGifUrl()') + return '' + } + url.hostname = 'k.gifs.bsky.app' + return url.href +} diff --git a/src/features/inviteFriends/InviteFriendsDialog.tsx b/src/features/inviteFriends/InviteFriendsDialog.tsx new file mode 100644 index 0000000000..7ca76f6e66 --- /dev/null +++ b/src/features/inviteFriends/InviteFriendsDialog.tsx @@ -0,0 +1,19 @@ +import * as Dialog from '#/components/Dialog' +import {InviteFriendsDialogInner} from './InviteFriendsDialogInner' + +export function InviteFriendsDialog({ + control, + onClose, +}: { + control: Dialog.DialogControlProps + onClose?: () => void +}) { + return ( + + + + ) +} diff --git a/src/features/inviteFriends/InviteFriendsDialog.web.tsx b/src/features/inviteFriends/InviteFriendsDialog.web.tsx new file mode 100644 index 0000000000..155c533918 --- /dev/null +++ b/src/features/inviteFriends/InviteFriendsDialog.web.tsx @@ -0,0 +1,13 @@ +import {type DialogControlProps} from '#/components/Dialog' + +/** + * Web no-op - the invite-friends share sheet is native-only by design. + * Callers may still create a Dialog control and pass it; opening it does + * nothing on web. + */ +export function InviteFriendsDialog(_props: { + control: DialogControlProps + onClose?: () => void +}) { + return null +} diff --git a/src/features/inviteFriends/InviteFriendsDialogInner.tsx b/src/features/inviteFriends/InviteFriendsDialogInner.tsx new file mode 100644 index 0000000000..16377c49f3 --- /dev/null +++ b/src/features/inviteFriends/InviteFriendsDialogInner.tsx @@ -0,0 +1,272 @@ +import {Suspense, useRef, useState} from 'react' +import {Pressable, View} from 'react-native' +import type ViewShot from 'react-native-view-shot' +import {setStringAsync} from 'expo-clipboard' +import {requestPermissionsAsync, saveToLibraryAsync} from 'expo-media-library' +import {useLingui} from '@lingui/react/macro' +import {useNavigation} from '@react-navigation/native' + +import {type NavigationProp} from '#/lib/routes/types' +import {shareUrl as nativeShareUrl} from '#/lib/sharing' +import {logger} from '#/logger' +import {useProfileQuery} from '#/state/queries/profile' +import {useSession} from '#/state/session' +import {atoms as a, useTheme} from '#/alf' +import {Button, ButtonText} from '#/components/Button' +import * as Dialog from '#/components/Dialog' +import {Divider} from '#/components/Divider' +import {Error as ErrorMessage} from '#/components/Error' +import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink' +import {Loader} from '#/components/Loader' +import * as Toast from '#/components/Toast' +import {Text} from '#/components/Typography' +import {useAnalytics} from '#/analytics' +import {IS_NATIVE} from '#/env' +import {ActionButtons} from './components/ActionButtons' +import {ThemedQrCard} from './components/ThemedQrCard' +import {ThemePicker} from './components/ThemePicker' +import {getInviteTheme, type InviteThemeKey} from './themes' +import {getInviteDisplayUrl, getInviteShareUrl} from './urls' + +export function InviteFriendsDialogInner({ + control, +}: { + control: Dialog.DialogControlProps +}) { + const {t: l} = useLingui() + const t = useTheme() + const ax = useAnalytics() + const navigation = useNavigation() + const {currentAccount} = useSession() + const profileQuery = useProfileQuery({did: currentAccount?.did}) + const [themeKey, setThemeKey] = useState('day') + const captureRef = useRef(null) + + const theme = getInviteTheme(themeKey) + const variant = t.name === 'light' ? theme.light : theme.dark + + const handle = profileQuery.data?.handle + const avatarUri = profileQuery.data?.avatar + + // 'handle.invalid' is the atproto sentinel for an unresolvable DID - treat + // it like a load error so we don't display a broken share URL. + const isHandleValid = !!handle && handle !== 'handle.invalid' + + const canonicalShareUrl = isHandleValid ? getInviteShareUrl(handle) : '' + const displayUrl = isHandleValid ? getInviteDisplayUrl(handle) : '' + + const onShare = async () => { + if (!canonicalShareUrl) { + Toast.show(l`Could not share – please try again`, {type: 'error'}) + return + } + ax.metric('invite:action:share', {}) + try { + await nativeShareUrl(canonicalShareUrl) + } catch (err) { + logger.error('InviteFriendsDialog: share failed', {safeMessage: err}) + } + } + + const onDownload = async () => { + if (!IS_NATIVE) return + if (!canonicalShareUrl) { + Toast.show(l`Could not download – please try again`, {type: 'error'}) + return + } + const uri = await captureRef.current?.capture?.() + if (!uri) { + Toast.show(l`Could not capture QR code`, {type: 'error'}) + return + } + + // Write-only permission - saving the QR image does not require read access + // to the user's photo library. + const permission = await requestPermissionsAsync(true) + if (!permission.granted) { + Toast.show( + l`You must grant access to your photo library to save a QR code`, + {type: 'error'}, + ) + return + } + + try { + // saveToLibraryAsync writes without reading the asset back, so it works + // with the add-only permission. createAssetAsync fetches the created + // asset, which triggers the full library read prompt on iOS (APP-2374). + await saveToLibraryAsync(`file://${uri}`) + ax.metric('invite:action:download', {}) + Toast.show(l`QR code saved to your camera roll!`) + } catch (err) { + logger.error('InviteFriendsDialog: download failed', {safeMessage: err}) + Toast.show(l`An error occurred while saving the QR code!`, { + type: 'error', + }) + } + } + + const onScan = () => { + ax.metric('invite:action:scan', {}) + // Close dialog first, then navigate (control.close callback per CLAUDE.md + // Dialog footgun rule — prevents race with the navigation push). + control.close(() => { + navigation.navigate('InviteScanner') + }) + } + + const onCopy = async () => { + if (!canonicalShareUrl) { + Toast.show(l`Could not copy – please try again`, {type: 'error'}) + return + } + try { + await setStringAsync(canonicalShareUrl) + ax.metric('invite:action:copy', {}) + Toast.show(l`Invite link copied`) + } catch (err) { + logger.error('InviteFriendsDialog: copy failed', {safeMessage: err}) + Toast.show(l`Failed to copy link`, {type: 'error'}) + } + } + + const onThemeChange = (next: InviteThemeKey) => { + ax.metric('invite:theme:change', {themeKey: next}) + setThemeKey(next) + } + + return ( + ( + + )}> + {l`Invite Friends`} + + }> + + + + + {profileQuery.isLoading ? ( + + + + ) : profileQuery.error || !isHandleValid ? ( + + profileQuery.refetch()} + hideBackButton + /> + + ) : ( + + + + }> + + + )} + + + + void onShare()} + onDownload={() => void onDownload()} + onScan={onScan} + /> + + + + + + {l({ + message: 'Invite link', + context: 'profile invite', + comment: + 'Link that invites someone to follow your profile, distinct from a group chat invite link', + })} + + + + + + + + {displayUrl || ' '} + + void onCopy()} + hitSlop={8} + style={({pressed}) => [{opacity: pressed ? 0.7 : 1}]}> + + {l`Copy`} + + + + + + ) +} diff --git a/src/features/inviteFriends/InviteScannerScreen.tsx b/src/features/inviteFriends/InviteScannerScreen.tsx new file mode 100644 index 0000000000..2887835c6b --- /dev/null +++ b/src/features/inviteFriends/InviteScannerScreen.tsx @@ -0,0 +1,264 @@ +import {useCallback, useState} from 'react' +import {Pressable, StyleSheet, useWindowDimensions, View} from 'react-native' +import {useSafeAreaInsets} from 'react-native-safe-area-context' +import Svg, {Path} from 'react-native-svg' +import { + type BarcodeScanningResult, + CameraView, + useCameraPermissions, +} from 'expo-camera' +import {useLingui} from '@lingui/react/macro' +import {useNavigation} from '@react-navigation/native' + +import {type NavigationProp} from '#/lib/routes/types' +import {atoms as a, useTheme} from '#/alf' +import {Button, ButtonText} from '#/components/Button' +import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeftIcon} from '#/components/icons/Arrow' +import {CircleInfo_Stroke2_Corner0_Rounded as InfoIcon} from '#/components/icons/CircleInfo' +import * as Layout from '#/components/Layout' +import {Text} from '#/components/Typography' +import {useAnalytics} from '#/analytics' + +const SCAN_FRAME_SIZE = 333 +const SCAN_FRAME_RADIUS = 16 + +export function InviteScannerScreen() { + const {t: l} = useLingui() + const t = useTheme() + const ax = useAnalytics() + const insets = useSafeAreaInsets() + const navigation = useNavigation() + const [permission, requestPermission] = useCameraPermissions() + const [showError, setShowError] = useState(false) + const [scannerEnabled, setScannerEnabled] = useState(true) + + const onClose = useCallback(() => { + navigation.goBack() + }, [navigation]) + + const onBarcodeScanned = useCallback( + (result: BarcodeScanningResult) => { + if (!scannerEnabled) return + const url = result.data?.trim() + if (!url) return + + // Match bsky.app/profile/{handle} URLs (with or without https://). + const profileMatch = url.match( + /^(?:https?:\/\/)?bsky\.app\/profile\/([^/?#]+)/i, + ) + if (!profileMatch) { + ax.metric('invite:scanner:scanned', {result: 'invalidQr'}) + setScannerEnabled(false) + setShowError(true) + return + } + + ax.metric('invite:scanner:scanned', {result: 'profileFound'}) + setScannerEnabled(false) + const handle = profileMatch[1] + navigation.replace('Profile', {name: handle}) + }, + [ax, scannerEnabled, navigation], + ) + + const onRetry = useCallback(() => { + setShowError(false) + setScannerEnabled(true) + }, []) + + // Permission states --------------------------------------------------------- + + if (!permission) { + // Still loading the initial permission state — render nothing. + return + } + + if (!permission.granted) { + return ( + + + + + {l`Scan QR code`} + + + + + + + {l`Camera access needed`} + + + {l`Bluesky needs camera access to scan QR codes from other profiles.`} + + + + + ) + } + + // Active scanner ------------------------------------------------------------ + + return ( + + + + + + {/* scan-frame outline (the colored border around the cutout) */} + + + + [ + { + width: 32, + height: 32, + alignItems: 'center', + justifyContent: 'center', + opacity: pressed ? 0.6 : 1, + }, + ]}> + + + + + {/* Error overlay shown when scanned QR isn't a valid profile URL */} + {showError && ( + + + + + {l`Profile not found`} + + + {l`Oops, this profile doesn't exist. Try scanning another one.`} + + + + + )} + + ) +} + +/** + * Renders a dark scrim covering the camera view with a rounded-rect cutout in + * the middle. Implemented as a single SVG Path (outer screen rect + inner + * rounded rect, even-odd fill) so the cutout's corners match the colored + * outline above it. + */ +function ScannerScrim() { + const {width, height} = useWindowDimensions() + const r = SCAN_FRAME_RADIUS + const half = SCAN_FRAME_SIZE / 2 + const x = width / 2 - half + const y = height / 2 - half + const right = x + SCAN_FRAME_SIZE + const bottom = y + SCAN_FRAME_SIZE + const d = + `M0 0 H${width} V${height} H0 Z ` + + `M${x + r} ${y} H${right - r} A${r} ${r} 0 0 1 ${right} ${y + r} ` + + `V${bottom - r} A${r} ${r} 0 0 1 ${right - r} ${bottom} ` + + `H${x + r} A${r} ${r} 0 0 1 ${x} ${bottom - r} ` + + `V${y + r} A${r} ${r} 0 0 1 ${x + r} ${y} Z` + return ( + + + + ) +} diff --git a/src/features/inviteFriends/InviteScannerScreen.web.tsx b/src/features/inviteFriends/InviteScannerScreen.web.tsx new file mode 100644 index 0000000000..5a5875f3e7 --- /dev/null +++ b/src/features/inviteFriends/InviteScannerScreen.web.tsx @@ -0,0 +1,45 @@ +import {View} from 'react-native' +import {useLingui} from '@lingui/react/macro' + +import {atoms as a, useTheme} from '#/alf' +import {CircleInfo_Stroke2_Corner0_Rounded as InfoIcon} from '#/components/icons/CircleInfo' +import * as Layout from '#/components/Layout' +import {Text} from '#/components/Typography' + +/** + * Web fallback - QR scanning relies on the device camera and is native-only by + * design. The route is registered on all platforms, so a web user reaching + * /invite/scan via a direct URL gets this message instead of the native + * camera UI. + */ +export function InviteScannerScreen() { + const {t: l} = useLingui() + const t = useTheme() + return ( + + + + + {l`Scan QR code`} + + + + + + + {l`Not available on this platform`} + + + {l`Please use the Bluesky mobile app to scan a QR code.`} + + + + ) +} diff --git a/src/features/inviteFriends/components/ActionButtons.tsx b/src/features/inviteFriends/components/ActionButtons.tsx new file mode 100644 index 0000000000..b96af58139 --- /dev/null +++ b/src/features/inviteFriends/components/ActionButtons.tsx @@ -0,0 +1,48 @@ +import {View} from 'react-native' +import {useLingui} from '@lingui/react/macro' + +import {atoms as a} from '#/alf' +import {StackedButton} from '#/components/Button' +import {ArrowShareRight_Stroke2_Corner2_Rounded as ShareIcon} from '#/components/icons/ArrowShareRight' +import {Camera_Stroke2_Corner0_Rounded as CameraIcon} from '#/components/icons/Camera' +import {Download_Stroke2_Corner0_Rounded as DownloadIcon} from '#/components/icons/Download' + +export function ActionButtons({ + onShare, + onDownload, + onScan, +}: { + onShare: () => void + onDownload: () => void + onScan: () => void +}) { + const {t: l} = useLingui() + return ( + + + {l`Share link`} + + + {l`Download`} + + + {l`Scan`} + + + ) +} diff --git a/src/features/inviteFriends/components/FollowersPromoBanner.tsx b/src/features/inviteFriends/components/FollowersPromoBanner.tsx new file mode 100644 index 0000000000..8701181599 --- /dev/null +++ b/src/features/inviteFriends/components/FollowersPromoBanner.tsx @@ -0,0 +1,101 @@ +import {useEffect} from 'react' +import {Pressable, View} from 'react-native' +import {Image} from 'expo-image' +import {useLingui} from '@lingui/react/macro' + +import {atoms as a, useTheme} from '#/alf' +import {TimesLarge_Stroke2_Corner0_Rounded as TimesIcon} from '#/components/icons/Times' +import {Text} from '#/components/Typography' +import {useAnalytics} from '#/analytics' + +export function FollowersPromoBanner({ + onPress, + onDismiss, +}: { + onPress: () => void + onDismiss: () => void +}) { + const {t: l} = useLingui() + const t = useTheme() + const ax = useAnalytics() + + useEffect(() => { + ax.metric('invite:followersPromo:seen', {}) + // Fire once per mount - parent unmounts the banner when followers > 0 or + // when dismissed, so each mount is a distinct impression. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + const handlePress = () => { + ax.metric('invite:followersPromo:press', {}) + onPress() + } + + const handleDismiss = () => { + ax.metric('invite:followersPromo:dismiss', {}) + onDismiss() + } + return ( + + {/* + * The dismiss button is a sibling of (not nested inside) the banner + * Pressable - nesting Pressables causes touch-handling conflicts on + * Android. This relative wrapper matches the banner bounds so the dismiss + * button can be positioned absolutely against the card's top-right corner. + */} + + [ + a.flex_row, + a.align_center, + a.w_full, + { + height: 80, + borderRadius: 16, + backgroundColor: t.palette.primary_50, + paddingHorizontal: 16, + paddingVertical: 16, + opacity: pressed ? 0.85 : 1, + }, + ]}> + + + {l`Import contacts or invite your friends`} + + + [ + { + position: 'absolute', + top: 8, + right: 8, + padding: 4, + opacity: pressed ? 0.5 : 1, + }, + ]}> + + + + + ) +} diff --git a/src/features/inviteFriends/components/ThemePicker.tsx b/src/features/inviteFriends/components/ThemePicker.tsx new file mode 100644 index 0000000000..179c80d815 --- /dev/null +++ b/src/features/inviteFriends/components/ThemePicker.tsx @@ -0,0 +1,120 @@ +import {Pressable, View} from 'react-native' +import {LinearGradient} from 'expo-linear-gradient' +import {useLingui} from '@lingui/react/macro' + +import {useTheme} from '#/alf' +import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron' +import * as Menu from '#/components/Menu' +import {Text} from '#/components/Typography' +import {INVITE_THEME_KEYS, INVITE_THEMES, type InviteThemeKey} from '../themes' + +const PILL_LABEL_SIZE = 13.1 +const SWATCH_SIZE = 14 +const CHEVRON_SIZE = 8 +const MENU_ITEM_SWATCH = 18 + +export function ThemePicker({ + value, + onChange, +}: { + value: InviteThemeKey + onChange: (next: InviteThemeKey) => void +}) { + const t = useTheme() + const {t: l} = useLingui() + const current = INVITE_THEMES[value] + + const labels: Record = { + dawn: l`Dawn`, + day: l`Day`, + dusk: l`Dusk`, + night: l`Night`, + } + + return ( + + + {({props}) => ( + ({ + flexDirection: 'row', + alignItems: 'center', + gap: 8, + paddingLeft: 12, + paddingRight: 14, + paddingVertical: 7, + borderRadius: 16, + backgroundColor: t.palette.contrast_50, + opacity: pressed ? 0.7 : 1, + })}> + + + + {l`Color`} + + + + + )} + + + + + {INVITE_THEME_KEYS.map(key => { + const theme = INVITE_THEMES[key] + return ( + onChange(key)}> + + {labels[key]} + + + ) + })} + + + + ) +} + +function GradientSwatch({ + from, + to, + size, +}: { + from: string + to: string + size: number +}) { + return ( + + ) +} diff --git a/src/features/inviteFriends/components/ThemedQrCard.tsx b/src/features/inviteFriends/components/ThemedQrCard.tsx new file mode 100644 index 0000000000..394d6ecca5 --- /dev/null +++ b/src/features/inviteFriends/components/ThemedQrCard.tsx @@ -0,0 +1,246 @@ +import {lazy} from 'react' +import {View} from 'react-native' +// @ts-expect-error missing types +import QRCode from 'react-native-qrcode-styled' +import type ViewShot from 'react-native-view-shot' +import {Image} from 'expo-image' +import {LinearGradient} from 'expo-linear-gradient' + +import {Logo} from '#/view/icons/Logo' +import {atoms as a, useTheme} from '#/alf' +import {hexToRgb, rgbToHex} from '#/alf/util/colorGeneration' +import {Text} from '#/components/Typography' +import {type InviteThemeVariant} from '../themes' + +const LazyViewShot = lazy( + // @ts-expect-error dynamic import + () => import('react-native-view-shot/src/index'), +) + +const CARD_WIDTH = 278 +const CARD_GRADIENT_PADDING = 12 +const QR_AREA = CARD_WIDTH - CARD_GRADIENT_PADDING * 2 +const QR_PIECE_SIZE = 7 +const QR_INNER_PADDING = 4 +const AVATAR_WRAPPER = 72 +const AVATAR_IMAGE = 60 +const AVATAR_BORDER = 2 + +export function ThemedQrCard({ + variant, + shareUrl, + handle, + avatarUri, + captureRef, +}: { + variant: InviteThemeVariant + shareUrl: string + handle: string + avatarUri?: string + captureRef: React.Ref +}) { + const t = useTheme() + return ( + + + + + + + + {avatarUri ? ( + + ) : ( + + + + )} + + + + + {`@${handle}`} + + + + + ) +} + +/** + * Returns the `gradient` prop shape that react-native-qrcode-styled accepts + * on both pieces and eyes. Mirrors the card's top-to-bottom LinearGradient + * so the QR data appears as a continuous extension of the card gradient + * rather than a separate solid-color overlay. + */ +function qrGradient(from: string, to: string) { + return { + gradient: { + type: 'linear' as const, + options: { + colors: [from, to], + start: [0.5, 0] as [number, number], + end: [0.5, 1] as [number, number], + }, + }, + } +} + +// Vertical positions (0..1 along the QR) where the corner eye centers sit. +// QR is 33 modules square; eye centers are at module index 3 and 29. +const EYE_TOP_T = 3 / 33 +const EYE_BOTTOM_T = 29 / 33 + +/** + * Returns the solid color the canvas-wide gradient would paint at vertical + * position `t` (0=top, 1=bottom). We use this to color each eye so that the + * eye blends seamlessly into the gradient on the surrounding data pieces. + * Eyes can't use a local `gradient` prop here - react-native-qrcode-styled + * 0.3.3 has an internal ID mismatch where the eye gradient are + * registered as `${pos}CornerSquareGradient` / `${pos}CornerDotGradient` + * but the Path fill references `url(#${pos}OuterEyeGradient)` / + * `url(#${pos}InnerEyeGradient)`, so per-eye gradients silently render + * with no fill. + */ +function eyeColor(from: string, to: string, t: number): string { + const fromRgb = hexToRgb(from) + const toRgb = hexToRgb(to) + if (!fromRgb || !toRgb) return from + return rgbToHex( + fromRgb.r + (toRgb.r - fromRgb.r) * t, + fromRgb.g + (toRgb.g - fromRgb.g) * t, + fromRgb.b + (toRgb.b - fromRgb.b) * t, + ) +} diff --git a/src/features/inviteFriends/components/ThemedQrCard.web.tsx b/src/features/inviteFriends/components/ThemedQrCard.web.tsx new file mode 100644 index 0000000000..bdecdc97b2 --- /dev/null +++ b/src/features/inviteFriends/components/ThemedQrCard.web.tsx @@ -0,0 +1,6 @@ +/** + * Web no-op - the invite-friends feature is native-only by design. + */ +export function ThemedQrCard() { + return null +} diff --git a/src/features/inviteFriends/hooks/useFollowersPromoDismissed.ts b/src/features/inviteFriends/hooks/useFollowersPromoDismissed.ts new file mode 100644 index 0000000000..90e2c0668f --- /dev/null +++ b/src/features/inviteFriends/hooks/useFollowersPromoDismissed.ts @@ -0,0 +1,12 @@ +import {device, useStorage} from '#/storage' + +/** + * Persisted dismiss state for the "Import contacts or invite your friends" + * promo banner shown on the Followers screen empty state. + */ +export function useFollowersPromoDismissed() { + const [dismissed = false, setDismissed] = useStorage(device, [ + 'inviteFriendsFollowersPromoDismissed', + ]) + return [dismissed, setDismissed] as const +} diff --git a/src/features/inviteFriends/index.ts b/src/features/inviteFriends/index.ts new file mode 100644 index 0000000000..f6e0416bbc --- /dev/null +++ b/src/features/inviteFriends/index.ts @@ -0,0 +1,4 @@ +export {FollowersPromoBanner} from './components/FollowersPromoBanner' +export {useFollowersPromoDismissed} from './hooks/useFollowersPromoDismissed' +export {InviteFriendsDialog} from './InviteFriendsDialog' +export {InviteScannerScreen} from './InviteScannerScreen' diff --git a/src/features/inviteFriends/themes.test.ts b/src/features/inviteFriends/themes.test.ts new file mode 100644 index 0000000000..6dbdfb2e9c --- /dev/null +++ b/src/features/inviteFriends/themes.test.ts @@ -0,0 +1,28 @@ +import {describe, expect, it} from '@jest/globals' + +import {getInviteTheme, INVITE_THEME_KEYS, INVITE_THEMES} from './themes' + +describe('invite themes', () => { + it('exposes four themes in canonical order', () => { + expect(INVITE_THEME_KEYS).toEqual(['dawn', 'day', 'dusk', 'night']) + }) + + it('has light and dark variants for every theme', () => { + for (const key of INVITE_THEME_KEYS) { + const theme = INVITE_THEMES[key] + expect(theme.light.qrPrimary).toMatch(/^#/) + expect(theme.dark.qrPrimary).toMatch(/^#/) + expect(theme.swatch).toMatch(/^#/) + } + }) + + it('getInviteTheme returns the requested theme', () => { + expect(getInviteTheme('day').swatch).toBe(INVITE_THEMES.day.swatch) + expect(getInviteTheme('night').swatch).toBe(INVITE_THEMES.night.swatch) + }) + + it('getInviteTheme falls back to day for unknown keys', () => { + // @ts-expect-error testing runtime fallback + expect(getInviteTheme('mystery').swatch).toBe(INVITE_THEMES.day.swatch) + }) +}) diff --git a/src/features/inviteFriends/themes.ts b/src/features/inviteFriends/themes.ts new file mode 100644 index 0000000000..6b71217372 --- /dev/null +++ b/src/features/inviteFriends/themes.ts @@ -0,0 +1,115 @@ +/** + * Color themes for the Invite Friends share sheet (APP-2142). + * + * Each theme has a light + dark pair. The QR halo / card border use the + * matching primary color; the QR data uses qrPrimary; the avatar handle text + * follows handleColor. + */ +export type InviteThemeKey = 'dawn' | 'day' | 'dusk' | 'night' + +export type InviteThemeVariant = { + /** QR data + eye color */ + qrPrimary: string + /** Card top-to-bottom gradient start (top) */ + gradientFrom: string + /** Card top-to-bottom gradient end (bottom) */ + gradientTo: string + /** Card drop-shadow color (opaque hex; opacity handled separately) */ + shadowColor: string + /** Handle label rendered under the QR (typically white) */ + handleColor: string +} + +export type InviteTheme = { + key: InviteThemeKey + /** Color shown in the theme picker dot */ + swatch: string + light: InviteThemeVariant + dark: InviteThemeVariant +} + +export const INVITE_THEME_KEYS: readonly InviteThemeKey[] = [ + 'dawn', + 'day', + 'dusk', + 'night', +] as const + +export const INVITE_THEMES: Record = { + dawn: { + key: 'dawn', + swatch: '#ff6dbe', + light: { + qrPrimary: '#ff6dbe', + gradientFrom: '#a8ccff', + gradientTo: '#ff6dbe', + shadowColor: '#ff6dbe', + handleColor: '#ffffff', + }, + dark: { + qrPrimary: '#ff6dbe', + gradientFrom: '#a8ccff', + gradientTo: '#ff6dbe', + shadowColor: '#ff6dbe', + handleColor: '#ffffff', + }, + }, + day: { + key: 'day', + swatch: '#006aff', + light: { + qrPrimary: '#006aff', + gradientFrom: '#006aff', + gradientTo: '#75afff', + shadowColor: '#006aff', + handleColor: '#ffffff', + }, + dark: { + qrPrimary: '#006aff', + gradientFrom: '#006aff', + gradientTo: '#75afff', + shadowColor: '#006aff', + handleColor: '#ffffff', + }, + }, + dusk: { + key: 'dusk', + swatch: '#f88f47', + light: { + qrPrimary: '#f88f47', + gradientFrom: '#f88f47', + gradientTo: '#b15aa2', + shadowColor: '#f88f47', + handleColor: '#ffffff', + }, + dark: { + qrPrimary: '#f88f47', + gradientFrom: '#f88f47', + gradientTo: '#b15aa2', + shadowColor: '#f88f47', + handleColor: '#ffffff', + }, + }, + night: { + key: 'night', + swatch: '#0048ad', + light: { + qrPrimary: '#0048ad', + gradientFrom: '#0048ad', + gradientTo: '#001533', + shadowColor: '#001533', + handleColor: '#ffffff', + }, + dark: { + qrPrimary: '#0048ad', + gradientFrom: '#0048ad', + gradientTo: '#001533', + shadowColor: '#001533', + handleColor: '#ffffff', + }, + }, +} + +export function getInviteTheme(key: InviteThemeKey): InviteTheme { + return INVITE_THEMES[key] ?? INVITE_THEMES.day +} diff --git a/src/features/inviteFriends/urls.test.ts b/src/features/inviteFriends/urls.test.ts new file mode 100644 index 0000000000..e24c3ca1cc --- /dev/null +++ b/src/features/inviteFriends/urls.test.ts @@ -0,0 +1,71 @@ +import {describe, expect, it} from '@jest/globals' + +import {getInviteDisplayUrl, getInviteShareUrl} from './urls' + +describe('invite URLs', () => { + describe('getInviteShareUrl', () => { + it('returns the canonical profile URL for a handle', () => { + expect(getInviteShareUrl('alice.bsky.social')).toBe( + 'https://bsky.app/profile/alice.bsky.social', + ) + }) + + it('preserves custom domains', () => { + expect(getInviteShareUrl('thepope.dev')).toBe( + 'https://bsky.app/profile/thepope.dev', + ) + }) + + it('strips a leading @ if present', () => { + expect(getInviteShareUrl('@alice.bsky.social')).toBe( + 'https://bsky.app/profile/alice.bsky.social', + ) + }) + + it('passes the handle through verbatim (handles are API-normalized, like makeProfileLink)', () => { + expect(getInviteShareUrl('Alice.Bsky.Social')).toBe( + 'https://bsky.app/profile/Alice.Bsky.Social', + ) + }) + + it('returns empty string for empty handle (EDGE-001)', () => { + expect(getInviteShareUrl('')).toBe('') + expect(getInviteShareUrl('@')).toBe('') + }) + }) + + describe('getInviteDisplayUrl', () => { + it('returns the scheme-stripped canonical profile URL', () => { + expect(getInviteDisplayUrl('danielle.bsky.team')).toBe( + 'bsky.app/profile/danielle.bsky.team', + ) + }) + + it('uses the bare handle when there is no dot', () => { + expect(getInviteDisplayUrl('alice')).toBe('bsky.app/profile/alice') + }) + + it('passes the handle through verbatim (handles are API-normalized, like makeProfileLink)', () => { + expect(getInviteDisplayUrl('Danielle.bsky.team')).toBe( + 'bsky.app/profile/Danielle.bsky.team', + ) + }) + + it('strips a leading @ if present', () => { + expect(getInviteDisplayUrl('@alice.bsky.social')).toBe( + 'bsky.app/profile/alice.bsky.social', + ) + }) + + it('returns empty string for empty handle (EDGE-001)', () => { + expect(getInviteDisplayUrl('')).toBe('') + expect(getInviteDisplayUrl('@')).toBe('') + }) + + it('mirrors getInviteShareUrl exactly minus the scheme', () => { + expect(getInviteDisplayUrl('alice.bsky.social')).toBe( + getInviteShareUrl('alice.bsky.social').replace(/^https:\/\//, ''), + ) + }) + }) +}) diff --git a/src/features/inviteFriends/urls.ts b/src/features/inviteFriends/urls.ts new file mode 100644 index 0000000000..7a22b8faf4 --- /dev/null +++ b/src/features/inviteFriends/urls.ts @@ -0,0 +1,31 @@ +/** + * URL helpers for the Invite Friends share sheet (APP-2142). + * + * Every action (QR payload, share sheet, clipboard) and the displayed label + * all derive from the same canonical `https://bsky.app/profile/{handle}` URL, + * so what the user reads matches exactly what they copy/share. The displayed + * label simply drops the `https://` scheme for readability. + * + * Kept as a dependency-free leaf module (no #/lib/strings/url-helpers import) + * so its unit tests stay fast and isolated from the heavy @atproto/api graph. + */ + +function stripLeadingAt(handle: string): string { + return handle.startsWith('@') ? handle.slice(1) : handle +} + +/** Canonical URL - used for QR payload, Share, and Copy. Empty handle -> empty string. */ +export function getInviteShareUrl(handle: string): string { + const bare = stripLeadingAt(handle) + if (!bare) return '' + return `https://bsky.app/profile/${bare}` +} + +/** + * Human-readable label shown in the "Invite link" field. This is the same + * canonical URL as getInviteShareUrl with the `https://` scheme stripped, so + * the displayed text always resolves and matches what Copy/Share use. + */ +export function getInviteDisplayUrl(handle: string): string { + return getInviteShareUrl(handle).replace(/^https:\/\//, '') +} diff --git a/src/features/liveEvents/components/LiveEventFeedCardCompact.tsx b/src/features/liveEvents/components/LiveEventFeedCardCompact.tsx index df2bb17f83..9d2f948eb6 100644 --- a/src/features/liveEvents/components/LiveEventFeedCardCompact.tsx +++ b/src/features/liveEvents/components/LiveEventFeedCardCompact.tsx @@ -69,6 +69,7 @@ export function LiveEventFeedCardCompact({ style={[a.absolute, a.inset_0, a.w_full, a.h_full]} contentFit="cover" placeholderContentFit="cover" + useAppleWebpCodec /> setImageLoadError(false)} onError={() => setImageLoadError(true)} + useAppleWebpCodec /> )} {linkMeta && (!linkMeta.image || imageLoadError) && ( diff --git a/src/features/liveNow/components/LiveStatusDialog.tsx b/src/features/liveNow/components/LiveStatusDialog.tsx index 545e8ab943..9e916678f2 100644 --- a/src/features/liveNow/components/LiveStatusDialog.tsx +++ b/src/features/liveNow/components/LiveStatusDialog.tsx @@ -147,6 +147,7 @@ export function LiveStatus({ contentFit="cover" style={[a.absolute, a.inset_0]} accessibilityIgnoresInvertColors + useAppleWebpCodec /> = { 'stream.place': 'Streamplace', 'skylight.social': 'Skylight', 'bluecast.app': 'Bluecast', + 'substack.com': 'Substack', + 'beehiiv.com': 'Beehiiv', } export function getLiveServiceNames(domains: Set) { diff --git a/src/lib/api/feed/author.ts b/src/lib/api/feed/author.ts index cc19f0f7a1..3b97b8ef73 100644 --- a/src/lib/api/feed/author.ts +++ b/src/lib/api/feed/author.ts @@ -1,20 +1,20 @@ import { AppBskyFeedDefs, type AppBskyFeedGetAuthorFeed as GetAuthorFeed, - type BskyAgent, + type AtpAgent, } from '@atproto/api' import {type FeedAPI, type FeedAPIResponse} from './types' export class AuthorFeedAPI implements FeedAPI { - agent: BskyAgent + agent: AtpAgent _params: GetAuthorFeed.QueryParams constructor({ agent, feedParams, }: { - agent: BskyAgent + agent: AtpAgent feedParams: GetAuthorFeed.QueryParams }) { this.agent = agent diff --git a/src/lib/api/feed/custom.ts b/src/lib/api/feed/custom.ts index 18bb8c8f07..54d9dc9067 100644 --- a/src/lib/api/feed/custom.ts +++ b/src/lib/api/feed/custom.ts @@ -1,7 +1,7 @@ import { type AppBskyFeedDefs, type AppBskyFeedGetFeed as GetCustomFeed, - BskyAgent, + AtpAgent, jsonStringToLex, } from '@atproto/api' @@ -13,7 +13,7 @@ import {type FeedAPI, type FeedAPIResponse} from './types' import {createBskyTopicsHeader, isBlueskyOwnedFeed} from './utils' export class CustomFeedAPI implements FeedAPI { - agent: BskyAgent + agent: AtpAgent params: GetCustomFeed.QueryParams userInterests?: string @@ -22,7 +22,7 @@ export class CustomFeedAPI implements FeedAPI { feedParams, userInterests, }: { - agent: BskyAgent + agent: AtpAgent feedParams: GetCustomFeed.QueryParams userInterests?: string }) { @@ -113,7 +113,7 @@ async function loggedOutFetch({ * @see https://github.com/bluesky-social/atproto/blob/60df3fc652b00cdff71dd9235d98a7a4bb828f05/packages/api/src/agent.ts#L120 */ const labelersHeader = { - 'atproto-accept-labelers': BskyAgent.appLabelers + 'atproto-accept-labelers': AtpAgent.appLabelers .map(l => `${l};redact`) .join(', '), } diff --git a/src/lib/api/feed/demo.ts b/src/lib/api/feed/demo.ts index 049e0f116e..42d1046bdc 100644 --- a/src/lib/api/feed/demo.ts +++ b/src/lib/api/feed/demo.ts @@ -1,12 +1,12 @@ -import {type AppBskyFeedDefs, type BskyAgent} from '@atproto/api' +import {type AppBskyFeedDefs, type AtpAgent} from '@atproto/api' import {DEMO_FEED} from '#/lib/demo' import {type FeedAPI, type FeedAPIResponse} from './types' export class DemoFeedAPI implements FeedAPI { - agent: BskyAgent + agent: AtpAgent - constructor({agent}: {agent: BskyAgent}) { + constructor({agent}: {agent: AtpAgent}) { this.agent = agent } diff --git a/src/lib/api/feed/following.ts b/src/lib/api/feed/following.ts index 26de7f8a07..17e96d8e1b 100644 --- a/src/lib/api/feed/following.ts +++ b/src/lib/api/feed/following.ts @@ -1,11 +1,11 @@ -import {type AppBskyFeedDefs, type BskyAgent} from '@atproto/api' +import {type AppBskyFeedDefs, type AtpAgent} from '@atproto/api' import {type FeedAPI, type FeedAPIResponse} from './types' export class FollowingFeedAPI implements FeedAPI { - agent: BskyAgent + agent: AtpAgent - constructor({agent}: {agent: BskyAgent}) { + constructor({agent}: {agent: AtpAgent}) { this.agent = agent } diff --git a/src/lib/api/feed/home.ts b/src/lib/api/feed/home.ts index 7a0d72d915..aa13c70bf0 100644 --- a/src/lib/api/feed/home.ts +++ b/src/lib/api/feed/home.ts @@ -1,4 +1,4 @@ -import {type AppBskyFeedDefs, type BskyAgent} from '@atproto/api' +import {type AppBskyFeedDefs, type AtpAgent} from '@atproto/api' import {PROD_DEFAULT_FEED} from '#/lib/constants' import {CustomFeedAPI} from './custom' @@ -27,7 +27,7 @@ export const FALLBACK_MARKER_POST: AppBskyFeedDefs.FeedViewPost = { } export class HomeFeedAPI implements FeedAPI { - agent: BskyAgent + agent: AtpAgent following: FollowingFeedAPI discover: CustomFeedAPI usingDiscover = false @@ -39,7 +39,7 @@ export class HomeFeedAPI implements FeedAPI { agent, }: { userInterests?: string - agent: BskyAgent + agent: AtpAgent }) { this.agent = agent this.following = new FollowingFeedAPI({agent}) diff --git a/src/lib/api/feed/likes.ts b/src/lib/api/feed/likes.ts index c970fa72fb..1511dc833a 100644 --- a/src/lib/api/feed/likes.ts +++ b/src/lib/api/feed/likes.ts @@ -1,20 +1,20 @@ import { type AppBskyFeedDefs, type AppBskyFeedGetActorLikes as GetActorLikes, - type BskyAgent, + type AtpAgent, } from '@atproto/api' import {type FeedAPI, type FeedAPIResponse} from './types' export class LikesFeedAPI implements FeedAPI { - agent: BskyAgent + agent: AtpAgent params: GetActorLikes.QueryParams constructor({ agent, feedParams, }: { - agent: BskyAgent + agent: AtpAgent feedParams: GetActorLikes.QueryParams }) { this.agent = agent diff --git a/src/lib/api/feed/merge.ts b/src/lib/api/feed/merge.ts index b3f9575dee..c341dd53a0 100644 --- a/src/lib/api/feed/merge.ts +++ b/src/lib/api/feed/merge.ts @@ -1,7 +1,7 @@ import { type AppBskyFeedDefs, type AppBskyFeedGetTimeline, - type BskyAgent, + type AtpAgent, } from '@atproto/api' import shuffle from 'lodash.shuffle' @@ -24,7 +24,7 @@ const POST_AGE_CUTOFF = 60e3 * 60 * 24 // 24hours export class MergeFeedAPI implements FeedAPI { userInterests?: string - agent: BskyAgent + agent: AtpAgent params: FeedParams feedTuners: FeedTunerFn[] following: MergeFeedSource_Following @@ -39,7 +39,7 @@ export class MergeFeedAPI implements FeedAPI { feedTuners, userInterests, }: { - agent: BskyAgent + agent: AtpAgent feedParams: FeedParams feedTuners: FeedTunerFn[] userInterests?: string @@ -175,7 +175,7 @@ export class MergeFeedAPI implements FeedAPI { } class MergeFeedSource { - agent: BskyAgent + agent: AtpAgent feedTuners: FeedTunerFn[] sourceInfo: ReasonFeedSource | undefined cursor: string | undefined = undefined @@ -186,7 +186,7 @@ class MergeFeedSource { agent, feedTuners, }: { - agent: BskyAgent + agent: AtpAgent feedTuners: FeedTunerFn[] }) { this.agent = agent @@ -253,7 +253,7 @@ class MergeFeedSource_Following extends MergeFeedSource { } class MergeFeedSource_Custom extends MergeFeedSource { - agent: BskyAgent + agent: AtpAgent minDate: Date feedUri: string userInterests?: string @@ -264,7 +264,7 @@ class MergeFeedSource_Custom extends MergeFeedSource { feedTuners, userInterests, }: { - agent: BskyAgent + agent: AtpAgent feedUri: string feedTuners: FeedTunerFn[] userInterests?: string diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts index 1973afb3c1..5a278aeba4 100644 --- a/src/lib/api/index.ts +++ b/src/lib/api/index.ts @@ -1,14 +1,15 @@ import { type $Typed, type AppBskyEmbedExternal, + type AppBskyEmbedGallery, type AppBskyEmbedImages, type AppBskyEmbedRecord, type AppBskyEmbedRecordWithMedia, type AppBskyEmbedVideo, - type AppBskyFeedPost, - AtUri, + AppBskyFeedPost, + type AtpAgent, BlobRef, - type BskyAgent, + ChatBskyGroupDefs, type ComAtprotoLabelDefs, type ComAtprotoRepoApplyWrites, type ComAtprotoRepoStrongRef, @@ -22,6 +23,7 @@ import {sha256} from 'js-sha256' import {CID} from 'multiformats/cid' import * as Hasher from 'multiformats/hashes/hasher' +import {IMAGE_SIZE_CONFIG_POSTS} from '#/lib/constants' import {isNetworkError} from '#/lib/strings/errors' import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip' import {logger} from '#/logger' @@ -39,6 +41,7 @@ import { type PostDraft, type ThreadDraft, } from '#/view/com/composer/state/composer' +import * as bsky from '#/types/bsky' import {createGIFDescription} from '../gif-alt-text' import {uploadBlob} from './upload-blob' @@ -51,16 +54,10 @@ interface PostOpts { langs?: string[] } -type FeatureFlags = { - highResolutionImages?: boolean - increasedBlobSizeLimit?: boolean -} - export async function post( - agent: BskyAgent, + agent: AtpAgent, queryClient: QueryClient, opts: PostOpts, - featureFlags?: FeatureFlags, ) { const thread = opts.thread opts.onStateChange?.(t`Processing...`) @@ -97,7 +94,6 @@ export async function post( queryClient, draft, opts.onStateChange, - featureFlags, ) let labels: $Typed | undefined if (draft.labels.length) { @@ -184,7 +180,8 @@ export async function post( writes: writes, validate: true, }) - } catch (e: any) { + } catch (err) { + const e = err as Error logger.error(`Failed to create post`, { safeMessage: e.message, }) @@ -200,7 +197,7 @@ export async function post( return {uris} } -async function resolveRT(agent: BskyAgent, richtext: RichText) { +async function resolveRT(agent: AtpAgent, richtext: RichText) { const trimmedText = richtext.text // Trim leading whitespace-only lines (but don't break ASCII art). .replace(/^(\s*\n)+/, '') @@ -214,32 +211,52 @@ async function resolveRT(agent: BskyAgent, richtext: RichText) { return rt } -async function resolveReply(agent: BskyAgent, replyTo: string) { - const replyToUrip = new AtUri(replyTo) - const parentPost = await agent.getPost({ - repo: replyToUrip.host, - rkey: replyToUrip.rkey, +export class ReplyDeletedError extends Error { + constructor() { + super('Could not resolve reply') + } +} + +async function resolveReply(agent: AtpAgent, replyTo: string) { + const {data} = await agent.app.bsky.feed.getPosts({ + uris: [replyTo], }) - if (parentPost) { - const parentRef = { - uri: parentPost.uri, - cid: parentPost.cid, - } - return { - root: parentPost.value.reply?.root || parentRef, - parent: parentRef, + const parentPost = data.posts[0] + if (!parentPost) { + throw new ReplyDeletedError() + } + + const parentRef = { + uri: parentPost.uri, + cid: parentPost.cid, + } + let rootRef = parentRef + + if ( + bsky.dangerousIsType( + parentPost.record, + AppBskyFeedPost.isRecord, + ) + ) { + if (parentPost.record.reply) { + rootRef = parentPost.record.reply.root } } + + return { + root: rootRef, + parent: parentRef, + } } async function resolveEmbed( - agent: BskyAgent, + agent: AtpAgent, queryClient: QueryClient, draft: PostDraft, onStateChange: ((state: string) => void) | undefined, - featureFlags?: FeatureFlags, ): Promise< | $Typed + | $Typed | $Typed | $Typed | $Typed @@ -248,13 +265,7 @@ async function resolveEmbed( > { if (draft.embed.quote) { const [resolvedMedia, resolvedQuote] = await Promise.all([ - resolveMedia( - agent, - queryClient, - draft.embed, - onStateChange, - featureFlags, - ), + resolveMedia(agent, queryClient, draft.embed, onStateChange), resolveRecord(agent, queryClient, draft.embed.quote.uri), ]) if (resolvedMedia) { @@ -277,7 +288,6 @@ async function resolveEmbed( queryClient, draft.embed, onStateChange, - featureFlags, ) if (resolvedMedia) { return resolvedMedia @@ -299,14 +309,14 @@ async function resolveEmbed( } async function resolveMedia( - agent: BskyAgent, + agent: AtpAgent, queryClient: QueryClient, embedDraft: EmbedDraft, onStateChange: ((state: string) => void) | undefined, - featureFlags?: FeatureFlags, ): Promise< | $Typed | $Typed + | $Typed | $Typed | undefined > { @@ -319,10 +329,10 @@ async function resolveMedia( const images: AppBskyEmbedImages.Image[] = await Promise.all( imagesDraft.map(async (image, i) => { logger.debug(`Compressing image #${i}`) - const {path, width, height, mime} = await compressImage(image, { - highResolution: featureFlags?.highResolutionImages, - increasedBlobSizeLimit: featureFlags?.increasedBlobSizeLimit, - }) + const {path, width, height, mime} = await compressImage( + image, + IMAGE_SIZE_CONFIG_POSTS, + ) logger.debug(`Uploading image #${i}`) const res = await uploadBlob(agent, path, mime) return { @@ -337,6 +347,34 @@ async function resolveMedia( images, } } + if (embedDraft.media?.type === 'gallery') { + const imagesDraft = embedDraft.media.images + logger.debug(`Uploading images`, { + count: imagesDraft.length, + }) + onStateChange?.(t`Uploading images...`) + const items: $Typed[] = await Promise.all( + imagesDraft.map(async (image, i) => { + logger.debug(`Compressing image #${i}`) + const {path, width, height, mime} = await compressImage( + image, + IMAGE_SIZE_CONFIG_POSTS, + ) + logger.debug(`Uploading image #${i}`) + const res = await uploadBlob(agent, path, mime) + return { + $type: 'app.bsky.embed.gallery#image' as const, + image: res.data.blob, + alt: image.alt, + aspectRatio: {width, height}, + } + }), + ) + return { + $type: 'app.bsky.embed.gallery', + items, + } + } if ( embedDraft.media?.type === 'video' && embedDraft.media.video.status === 'done' @@ -422,6 +460,20 @@ async function resolveMedia( title: resolvedLink.title, description: resolvedLink.description, thumb: blob, + associatedRefs: resolvedLink.associatedRefs, + }, + } + } + if ( + resolvedLink.type === 'chat-invite' && + ChatBskyGroupDefs.isJoinLinkPreviewView(resolvedLink.view) + ) { + return { + $type: 'app.bsky.embed.external', + external: { + uri: resolvedLink.uri, + title: resolvedLink.view.name, + description: `${resolvedLink.view.memberCount}/${resolvedLink.view.memberLimit}`, }, } } @@ -430,7 +482,7 @@ async function resolveMedia( } async function resolveRecord( - agent: BskyAgent, + agent: AtpAgent, queryClient: QueryClient, uri: string, ): Promise { @@ -468,6 +520,7 @@ async function computeCid(record: AppBskyFeedPost.Record): Promise { } // Returns a transformed version of the object for use in DAG-CBOR. +// eslint-disable-next-line @typescript-eslint/no-explicit-any function prepareForHashing(v: any): any { // IMPORTANT: BlobRef#ipld() returns the correct object we need for hashing, // the API client will convert this for you but we're hashing in the client, @@ -490,9 +543,10 @@ function prepareForHashing(v: any): any { // Walk through plain objects if (isPlainObject(v)) { - const obj: any = {} + const obj: Record = {} let pure = true for (const key in v) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access let value = v[key] // `value` is undefined if (value === undefined) { @@ -511,6 +565,7 @@ function prepareForHashing(v: any): any { return v } +// eslint-disable-next-line @typescript-eslint/no-explicit-any function isPlainObject(v: any): boolean { if (typeof v !== 'object' || v === null) { return false diff --git a/src/lib/api/resolve.ts b/src/lib/api/resolve.ts index e6d21845fd..476c0f1a20 100644 --- a/src/lib/api/resolve.ts +++ b/src/lib/api/resolve.ts @@ -1,13 +1,13 @@ import { type AppBskyFeedDefs, type AppBskyGraphDefs, + type AtpAgent, type ComAtprotoRepoStrongRef, } from '@atproto/api' import {AtUri} from '@atproto/api' -import {type BskyAgent} from '@atproto/api' -import {POST_IMG_MAX} from '#/lib/constants' -import {getLinkMeta} from '#/lib/link-meta/link-meta' +import {DM_SERVICE_HEADERS, IMAGE_SIZE_CONFIG_2K_1MB} from '#/lib/constants' +import {getLinkMeta, type LinkMeta} from '#/lib/link-meta/link-meta' import {resolveShortLink} from '#/lib/link-meta/resolve-short-link' import {downloadAndResize} from '#/lib/media/manip' import { @@ -15,18 +15,21 @@ import { parseStarterPackUri, } from '#/lib/strings/starter-pack' import { + convertBskyAppUrlIfNeeded, + getChatInviteCodeFromUrl, isBskyCustomFeedUrl, isBskyListUrl, isBskyPostUrl, isBskyStarterPackUrl, isBskyStartUrl, isShortLink, + makeRecordUri, } from '#/lib/strings/url-helpers' import {type ComposerImage} from '#/state/gallery' import {createComposerImage} from '#/state/gallery' -import {type Gif} from '#/state/queries/tenor' +import {type ChatInvitePreview} from '#/state/queries/join-links' +import {type Gif} from '#/features/gifPicker/types' import {createGIFDescription} from '../gif-alt-text' -import {convertBskyAppUrlIfNeeded, makeRecordUri} from '../strings/url-helpers' type ResolvedExternalLink = { type: 'external' @@ -34,6 +37,12 @@ type ResolvedExternalLink = { title: string description: string thumb: ComposerImage | undefined + /** + * The AT-URI of the Atmosphere record representing this external content, if + * it exists. Example: a site.standard.document record. + */ + associatedRefs?: LinkMeta['associatedRefs'] + view?: LinkMeta['view'] } type ResolvedPostRecord = { @@ -64,12 +73,20 @@ type ResolvedStarterPackRecord = { view: AppBskyGraphDefs.StarterPackView } +type ResolvedChatInvite = { + type: 'chat-invite' + uri: string + code: string + view?: ChatInvitePreview +} + export type ResolvedLink = | ResolvedExternalLink | ResolvedPostRecord | ResolvedFeedRecord | ResolvedListRecord | ResolvedStarterPackRecord + | ResolvedChatInvite export class EmbeddingDisabledError extends Error { constructor() { @@ -78,7 +95,7 @@ export class EmbeddingDisabledError extends Error { } export async function resolveLink( - agent: BskyAgent, + agent: AtpAgent, uri: string, ): Promise { if (isShortLink(uri)) { @@ -134,6 +151,19 @@ export async function resolveLink( view: res.data.list, } } + const chatInviteCode = getChatInviteCodeFromUrl(uri) + if (chatInviteCode) { + const res = await agent.chat.bsky.group.getJoinLinkPreviews( + {codes: [chatInviteCode]}, + {headers: DM_SERVICE_HEADERS}, + ) + return { + type: 'chat-invite', + uri, + code: chatInviteCode, + view: res.data.joinLinkPreviews[0], + } + } if (isBskyStartUrl(uri) || isBskyStarterPackUrl(uri)) { const parsed = parseStarterPackUri(uri) if (!parsed) { @@ -187,7 +217,7 @@ export async function resolveLink( } export async function resolveGif( - agent: BskyAgent, + agent: AtpAgent, gif: Gif, ): Promise { const gifUrl = gif.media_formats.gif.url @@ -229,7 +259,7 @@ function getFileSlug(url: string | undefined): string | undefined { } async function resolveExternal( - agent: BskyAgent, + agent: AtpAgent, uri: string, ): Promise { const result = await getLinkMeta(agent, uri) @@ -239,6 +269,12 @@ async function resolveExternal( title: result.title ?? '', description: result.description ?? '', thumb: result.image ? await imageToThumb(result.image) : undefined, + /* + * New fields from Standard Site integration. Other fields are derived from + * opengraph/oembed as before. + */ + associatedRefs: result.associatedRefs, + view: result.view, } } @@ -248,10 +284,7 @@ export async function imageToThumb( try { const img = await downloadAndResize({ uri: imageUri, - width: POST_IMG_MAX.width, - height: POST_IMG_MAX.height, - mode: 'contain', - maxSize: POST_IMG_MAX.size, + ...IMAGE_SIZE_CONFIG_2K_1MB, timeout: 15e3, }) if (img) { diff --git a/src/lib/api/upload-blob.ts b/src/lib/api/upload-blob.ts index 260ba770b8..0bee39b9b1 100644 --- a/src/lib/api/upload-blob.ts +++ b/src/lib/api/upload-blob.ts @@ -1,5 +1,5 @@ import {copyAsync} from 'expo-file-system/legacy' -import {type BskyAgent, type ComAtprotoRepoUploadBlob} from '@atproto/api' +import {type AtpAgent, type ComAtprotoRepoUploadBlob} from '@atproto/api' import {safeDeleteAsync} from '#/lib/media/manip' @@ -7,7 +7,7 @@ import {safeDeleteAsync} from '#/lib/media/manip' * @param encoding Allows overriding the blob's type */ export async function uploadBlob( - agent: BskyAgent, + agent: AtpAgent, input: string | Blob, encoding?: string, ): Promise { diff --git a/src/lib/api/upload-blob.web.ts b/src/lib/api/upload-blob.web.ts index 9f21be5670..d74e834647 100644 --- a/src/lib/api/upload-blob.web.ts +++ b/src/lib/api/upload-blob.web.ts @@ -1,4 +1,4 @@ -import {type BskyAgent, type ComAtprotoRepoUploadBlob} from '@atproto/api' +import {type AtpAgent, type ComAtprotoRepoUploadBlob} from '@atproto/api' /** * @note It is recommended, on web, to use the `file` instance of the file @@ -7,7 +7,7 @@ import {type BskyAgent, type ComAtprotoRepoUploadBlob} from '@atproto/api' * be passed directly to this function. */ export async function uploadBlob( - agent: BskyAgent, + agent: AtpAgent, input: string | Blob, encoding?: string, ): Promise { diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 8364cd2f6c..91d1319c6b 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -2,7 +2,7 @@ import {type Insets, Platform} from 'react-native' import {type AppBskyActorDefs, BSKY_LABELER_DID} from '@atproto/api' import {type ProxyHeaderValue} from '#/state/session/agent' -import {BLUESKY_PROXY_DID, CHAT_PROXY_DID} from '#/env' +import {BLUESKY_PROXY_DID, CHAT_PROXY_DID, IS_DEV} from '#/env' export const LOCAL_DEV_SERVICE = Platform.OS === 'android' ? 'http://10.0.2.2:2583' : 'http://localhost:2583' @@ -13,6 +13,7 @@ export const PUBLIC_BSKY_SERVICE = 'https://public.api.bsky.app' export const DEFAULT_SERVICE = BSKY_SERVICE const HELP_DESK_LANG = 'en-us' export const HELP_DESK_URL = `https://blueskyweb.zendesk.com/hc/${HELP_DESK_LANG}` +export const CHAT_SERVICE = 'https://api.bsky.chat' export const EMBED_SERVICE = 'https://embed.bsky.app' export const EMBED_SCRIPT = `${EMBED_SERVICE}/static/embed.js` export const BSKY_DOWNLOAD_URL = 'https://bsky.app/download' @@ -66,6 +67,8 @@ export const MAX_DRAFT_GRAPHEME_LENGTH = 1000 export const MAX_DM_GRAPHEME_LENGTH = 1000 +export const MAX_GROUP_NAME_GRAPHEME_LENGTH = 50 + // Recommended is 100 per: https://www.w3.org/WAI/GL/WCAG20/tests/test3.html // but increasing limit per user feedback export const MAX_ALT_TEXT = 2000 @@ -96,10 +99,14 @@ export const STAGING_FEEDS = [ `feedgen|${STAGING_DEFAULT_FEED('thevids')}`, ] -export const POST_IMG_MAX = { - width: 2000, - height: 2000, - size: 1000000, +export const IMAGE_SIZE_CONFIG_POSTS = { + maxDimension: 4000, + maxSize: 2000000, +} + +export const IMAGE_SIZE_CONFIG_2K_1MB = { + maxDimension: 2000, + maxSize: 1000000, } export const STAGING_LINK_META_PROXY = @@ -107,12 +114,12 @@ export const STAGING_LINK_META_PROXY = export const PROD_LINK_META_PROXY = 'https://cardyb.bsky.app/v1/extract?url=' -export function LINK_META_PROXY(serviceUrl: string) { - if (IS_PROD_SERVICE(serviceUrl)) { - return PROD_LINK_META_PROXY +export function LINK_META_PROXY(_serviceUrl: string) { + if (IS_DEV) { + return STAGING_LINK_META_PROXY } - return STAGING_LINK_META_PROXY + return PROD_LINK_META_PROXY } export const STATUS_PAGE_URL = 'https://status.bsky.app/' @@ -173,11 +180,6 @@ export const KNOWN_SHUTDOWN_FEEDS = [ export const GIF_SERVICE = 'https://gifs.bsky.app' -export const GIF_SEARCH = (params: string) => - `${GIF_SERVICE}/tenor/v2/search?${params}` -export const GIF_FEATURED = (params: string) => - `${GIF_SERVICE}/tenor/v2/featured?${params}` - export const GIF_KLIPY_SEARCH = (params: string) => `${GIF_SERVICE}/klipy/v2/search?${params}` export const GIF_KLIPY_FEATURED = (params: string) => @@ -193,7 +195,8 @@ export const VIDEO_MAX_DURATION_MS = 3 * 60 * 1000 // 3 minutes in milliseconds * Maximum size of a video in megabytes, _not_ mebibytes. Backend uses * ISO megabytes. */ -export const VIDEO_MAX_SIZE = 1000 * 1000 * 100 // 100mb +export const VIDEO_MAX_SIZE_MB = 300 +export const VIDEO_MAX_SIZE = VIDEO_MAX_SIZE_MB * 1000 * 1000 // 300mb export const SUPPORTED_MIME_TYPES = [ 'video/mp4', diff --git a/src/lib/generate-starterpack.ts b/src/lib/generate-starterpack.ts index 1f4265a17e..a53e95a6a3 100644 --- a/src/lib/generate-starterpack.ts +++ b/src/lib/generate-starterpack.ts @@ -2,7 +2,7 @@ import { type $Typed, type AppBskyActorDefs, type AppBskyGraphGetStarterPack, - type BskyAgent, + type AtpAgent, type ComAtprotoRepoApplyWrites, type Facet, } from '@atproto/api' @@ -28,7 +28,7 @@ export const createStarterPackList = async ({ description?: string descriptionFacets?: Facet[] profiles: bsky.profile.AnyProfileView[] - agent: BskyAgent + agent: AtpAgent }): Promise<{uri: string; cid: string}> => { if (profiles.length === 0) throw new Error('No profiles given') @@ -152,7 +152,7 @@ function createListItem({ } async function whenAppViewReady( - agent: BskyAgent, + agent: AtpAgent, uri: string, fn: (res?: AppBskyGraphGetStarterPack.Response) => boolean, ) { diff --git a/src/lib/hooks/useCleanError.ts b/src/lib/hooks/useCleanError.ts index c7e085e68f..5f8dce6081 100644 --- a/src/lib/hooks/useCleanError.ts +++ b/src/lib/hooks/useCleanError.ts @@ -1,6 +1,5 @@ import {useCallback} from 'react' -import {msg} from '@lingui/core/macro' -import {useLingui} from '@lingui/react' +import {useLingui} from '@lingui/react/macro' type CleanedError = { raw: string | undefined @@ -8,9 +7,9 @@ type CleanedError = { } export function useCleanError() { - const {_} = useLingui() + const {t: l} = useLingui() - return useCallback<(error?: any) => CleanedError>( + return useCallback<(error?: unknown) => CleanedError>( error => { if (!error) return { @@ -18,14 +17,17 @@ export function useCleanError() { clean: undefined, } - let raw = error.toString() + let raw = + error instanceof Error + ? error.message + : typeof error === 'string' + ? error + : JSON.stringify(error) if (isNetworkError(raw)) { return { raw, - clean: _( - msg`Unable to connect. Please check your internet connection and try again.`, - ), + clean: l`Unable to connect. Please check your internet connection and try again.`, } } @@ -36,9 +38,7 @@ export function useCleanError() { ) { return { raw, - clean: _( - msg`The server appears to be experiencing issues. Please try again in a few moments.`, - ), + clean: l`The server appears to be experiencing issues. Please try again in a few moments.`, } } @@ -51,27 +51,21 @@ export function useCleanError() { ) { return { raw, - clean: _( - msg`You cannot update your birthdate while using an app password. Please sign in with your main password to update your birthdate.`, - ), + clean: l`You cannot update your birthdate while using an app password. Please sign in with your main password to update your birthdate.`, } } if (raw.includes('Bad token scope') || raw.includes('Bad token method')) { return { raw, - clean: _( - msg`This feature is not available while using an app password. Please sign in with your main password.`, - ), + clean: l`This feature is not available while using an app password. Please sign in with your main password.`, } } if (raw.includes('Rate Limit Exceeded')) { return { raw, - clean: _( - msg`You've reached the maximum number of requests allowed. Please try again later.`, - ), + clean: l`You've reached the maximum number of requests allowed. Please try again later.`, } } @@ -84,7 +78,7 @@ export function useCleanError() { clean: undefined, } }, - [_], + [l], ) } diff --git a/src/lib/hooks/useGoogleTranslate.ts b/src/lib/hooks/useGoogleTranslate.ts index 70f1637bd8..15bec91a26 100644 --- a/src/lib/hooks/useGoogleTranslate.ts +++ b/src/lib/hooks/useGoogleTranslate.ts @@ -50,10 +50,10 @@ export function useGoogleTranslate() { } catch (err) { if (__DEV__) console.error(err) // most likely means they don't have the translate app - await openLink(translateUrl) + openLink(translateUrl) } } else { - await openLink(translateUrl) + openLink(translateUrl) } }, [openLink], diff --git a/src/lib/hooks/useIntentHandler.ts b/src/lib/hooks/useIntentHandler.ts index c35ca49b16..5a9d0b39fe 100644 --- a/src/lib/hooks/useIntentHandler.ts +++ b/src/lib/hooks/useIntentHandler.ts @@ -5,7 +5,11 @@ import * as WebBrowser from 'expo-web-browser' import {useOpenComposer} from '#/lib/hooks/useOpenComposer' import {parseLinkingUrl} from '#/lib/parseLinkingUrl' +import {CHAT_INVITE_CODE_REGEX} from '#/lib/strings/url-helpers' +import {usePrefetchJoinLinkPreviews} from '#/state/queries/join-links' import {useSession} from '#/state/session' +import {useSetActiveLanding} from '#/state/shell/landing' +import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {useCloseAllActiveElements} from '#/state/util' import {useIntentDialogs} from '#/components/intents/IntentDialogs' import {useAnalytics} from '#/analytics' @@ -25,6 +29,7 @@ export function useIntentHandler() { const ax = useAnalytics() const composeIntent = useComposeIntent() const verifyEmailIntent = useVerifyEmailIntent() + const groupChatJoinIntent = useGroupChatJoinIntent() const {currentAccount} = useSession() const {tryApplyUpdate} = useApplyPullRequestOTAUpdate() @@ -44,6 +49,11 @@ export function useIntentHandler() { }) } const urlp = parseLinkingUrl(url) + const chatInviteMatch = urlp.pathname.match(CHAT_INVITE_CODE_REGEX) + if (chatInviteMatch) { + groupChatJoinIntent(chatInviteMatch[1], url) + return + } const [, intent, intentType] = urlp.pathname.split('/') // On native, our links look like bluesky://intent/SomeIntent, so we have to check the hostname for the @@ -99,6 +109,7 @@ export function useIntentHandler() { ax, composeIntent, verifyEmailIntent, + groupChatJoinIntent, currentAccount, tryApplyUpdate, ]) @@ -162,6 +173,46 @@ export function useComposeIntent() { ) } +export function useGroupChatJoinIntent() { + const closeAllActiveElements = useCloseAllActiveElements() + const {hasSession} = useSession() + const {groupChatJoinDialogControl: control, setGroupChatJoinState: setState} = + useIntentDialogs() + const {requestSwitchToAccount} = useLoggedOutViewControls() + const setActiveLanding = useSetActiveLanding() + const prefetchJoinLinkPreviews = usePrefetchJoinLinkPreviews() + return useCallback( + (code: string, uri?: string) => { + closeAllActiveElements() + if (hasSession) { + setState({code}) + const prefetch = prefetchJoinLinkPreviews({ + codes: [code], + hasSession: true, + }) + void Promise.race([ + prefetch, + new Promise(res => setTimeout(res, 200)), + ]).finally(() => { + control.open() + }) + } else { + setActiveLanding({type: 'groupchat', uri: uri ?? '', code}) + requestSwitchToAccount({requestedAccount: 'groupchat'}) + } + }, + [ + closeAllActiveElements, + hasSession, + control, + setState, + prefetchJoinLinkPreviews, + requestSwitchToAccount, + setActiveLanding, + ], + ) +} + function useVerifyEmailIntent() { const closeAllActiveElements = useCloseAllActiveElements() const {verifyEmailDialogControl: control, setVerifyEmailState: setState} = diff --git a/src/lib/hooks/useMinimalShellTransform.ts b/src/lib/hooks/useMinimalShellTransform.ts index fc3bb9c9f7..11c08e4dc5 100644 --- a/src/lib/hooks/useMinimalShellTransform.ts +++ b/src/lib/hooks/useMinimalShellTransform.ts @@ -1,62 +1,10 @@ import {interpolate, useAnimatedStyle} from 'react-native-reanimated' -import {useSafeAreaInsets} from 'react-native-safe-area-context' import {useMinimalShellMode} from '#/state/shell/minimal-mode' import {useShellLayout} from '#/state/shell/shell-layout' -import {IS_LIQUID_GLASS} from '#/env' // Keep these separated so that we only pay for useAnimatedStyle that gets used. -export function useMinimalShellHeaderTransform() { - const {headerMode} = useMinimalShellMode() - const {headerHeight} = useShellLayout() - const {top: topInset} = useSafeAreaInsets() - - const headerPinnedHeight = IS_LIQUID_GLASS ? topInset : 0 - - const headerTransform = useAnimatedStyle(() => { - const headerModeValue = headerMode.get() - const hHeight = headerHeight.get() - - if (IS_LIQUID_GLASS) { - // bit of a hackfix, but: the header can get affected by scrollEdgeEffects - // when animating from closed to open. workaround is to trigger a relayout - // by offsetting the top position. the actual value doesn't matter, and we - // simultaneously offset it using the translate transform. - // I think a cleaner way to do it would be to use UIScrollEdgeElementContainerInteraction - // manually or something like that, because this kinda sucks -sfn - const relayoutingOffset = headerModeValue === 0 ? 1 : 0 - return { - top: relayoutingOffset, - pointerEvents: headerModeValue === 0 ? 'auto' : 'none', - opacity: Math.pow(1 - headerModeValue, 2), - transform: [ - { - translateY: - interpolate( - headerModeValue, - [0, 1], - [0, headerPinnedHeight - hHeight], - ) - relayoutingOffset, - }, - ], - } - } - - return { - pointerEvents: headerModeValue === 0 ? 'auto' : 'none', - opacity: Math.pow(1 - headerModeValue, 2), - transform: [ - { - translateY: interpolate(headerModeValue, [0, 1], [0, -hHeight]), - }, - ], - } - }) - - return headerTransform -} - export function useMinimalShellFooterTransform() { const {footerMode} = useMinimalShellMode() const {footerHeight} = useShellLayout() diff --git a/src/lib/hooks/useNotificationHandler.ts b/src/lib/hooks/useNotificationHandler.ts index 60d6ebabfe..9621cafc88 100644 --- a/src/lib/hooks/useNotificationHandler.ts +++ b/src/lib/hooks/useNotificationHandler.ts @@ -30,6 +30,9 @@ export type NotificationReason = | 'quote' | 'chat-message' | 'chat-reaction' + | 'chat-added-to-group' + | 'chat-removed-from-group' + | 'chat-join-request-rejected' | 'starterpack-joined' | 'like-via-repost' | 'repost-via-repost' @@ -37,6 +40,8 @@ export type NotificationReason = | 'unverified' | 'subscribed-post' +type ChatNotificationReason = Extract + /** * Manually overridden type, but retains the possibility of * `notification.request.trigger.payload` being `undefined`, as specified in @@ -45,7 +50,7 @@ export type NotificationReason = export type NotificationPayload = | undefined | { - reason: Exclude + reason: Exclude uri: string subject: string recipientDid: string @@ -62,6 +67,25 @@ export type NotificationPayload = messageId: string recipientDid: string } + | { + reason: + | 'chat-added-to-group' + | 'chat-removed-from-group' + | 'chat-join-request-rejected' + convoId: string + recipientDid: string + } + +export type ChatNotificationPayload = Extract< + NonNullable, + {reason: ChatNotificationReason} +> + +export function isChatNotificationPayload( + payload: NonNullable, +): payload is ChatNotificationPayload { + return payload.reason.startsWith('chat-') +} const DEFAULT_HANDLER_OPTIONS = { shouldShowBanner: false, @@ -199,10 +223,7 @@ export function useNotificationsHandler() { const handleNotification = (payload?: NotificationPayload) => { if (!payload) return - if ( - payload.reason === 'chat-message' || - payload.reason === 'chat-reaction' - ) { + if (isChatNotificationPayload(payload)) { logger.debug(`useNotificationsHandler: handling chat notification`, { payload, }) @@ -220,7 +241,13 @@ export function useNotificationsHandler() { } else { setShowLoggedOut(true) } - } else { + } else if ( + payload.reason === 'chat-message' || + payload.reason === 'chat-reaction' || + payload.reason === 'chat-added-to-group' + ) { + // chat-added-to-group routes to the convo because the recipient was + // just added and now has access. navigation.dispatch(state => { if (state.routes[0].name === 'Messages') { if ( @@ -253,6 +280,12 @@ export function useNotificationsHandler() { }) } }) + } else { + // chat-removed-from-group, chat-join-request-rejected: the convo is + // no longer accessible to the recipient, so just open the list. + navigation.dispatch( + CommonActions.navigate('MessagesTab', {screen: 'Messages'}), + ) } } else { const url = notificationToURL(payload) @@ -280,11 +313,16 @@ export function useNotificationsHandler() { logger.debug('useNotificationsHandler: incoming', {e, payload}) if ( - (payload.reason === 'chat-message' || - payload.reason === 'chat-reaction') && + isChatNotificationPayload(payload) && payload.recipientDid === currentAccount?.did ) { - const shouldAlert = payload.convoId !== currentConvoId + // chat-removed-from-group / chat-join-request-rejected always alert, + // even if the recipient is currently viewing the affected convo - + // they need to know they were removed/rejected. + const shouldAlert = + payload.reason === 'chat-removed-from-group' || + payload.reason === 'chat-join-request-rejected' || + payload.convoId !== currentConvoId return { shouldShowList: shouldAlert, shouldShowBanner: shouldAlert, @@ -342,6 +380,18 @@ export function useNotificationsHandler() { handleNotification(payload) Notifications.dismissAllNotificationsAsync() + // Also clear the native `lastResponse` cache. Otherwise a subsequent + // `getLastNotificationResponse()` (e.g. on an account-switch remount, + // which re-runs `handlePushNotificationEntry`) would replay this + // payload and ping-pong the user back to the previous account/chat. + try { + Notifications.clearLastNotificationResponse() + } catch (error) { + notyLogger.error( + `useNotificationsHandler: error clearing notification response`, + {error}, + ) + } } else { logger.error('useNotificationsHandler: received no payload', { identifier: e.notification.request.identifier, @@ -352,8 +402,8 @@ export function useNotificationsHandler() { // Whenever there's a stored payload, that means we had to switch accounts before handling the notification. // Whenever currentAccount changes, we should try to handle it again. if ( - (storedAccountSwitchPayload?.reason === 'chat-message' || - storedAccountSwitchPayload?.reason === 'chat-reaction') && + storedAccountSwitchPayload && + isChatNotificationPayload(storedAccountSwitchPayload) && currentAccount?.did === storedAccountSwitchPayload.recipientDid ) { handleNotification(storedAccountSwitchPayload) @@ -442,6 +492,9 @@ export function notificationToURL(payload: NotificationPayload): string | null { } case 'chat-message': case 'chat-reaction': + case 'chat-added-to-group': + case 'chat-removed-from-group': + case 'chat-join-request-rejected': // should be handled separately return null case 'verified': diff --git a/src/lib/hooks/useOpenLink.ts b/src/lib/hooks/useOpenLink.ts index 9c6e75ecb8..4fb9c6d7cc 100644 --- a/src/lib/hooks/useOpenLink.ts +++ b/src/lib/hooks/useOpenLink.ts @@ -26,7 +26,7 @@ export function useOpenLink() { const {inAppBrowserConsentControl} = useGlobalDialogsControlContext() const openLink = useCallback( - async (url: string, override?: boolean, shouldProxy?: boolean) => { + (url: string, override?: boolean, shouldProxy?: boolean) => { if (isBskyRSSUrl(url) && isRelativeUrl(url)) { url = createBskyAppAbsoluteUrl(url) } @@ -66,12 +66,12 @@ export function useOpenLink() { }).catch(err => { if (__DEV__) logger.error('Could not open web browser', {message: err}) - Linking.openURL(url) + void Linking.openURL(url) }) return } } - Linking.openURL(url) + void Linking.openURL(url) }, [ax, enabled, inAppBrowserConsentControl, t, dialogContext], ) diff --git a/src/lib/hooks/useTimeAgo.ts b/src/lib/hooks/useTimeAgo.ts index 67398ec547..ffc808a91f 100644 --- a/src/lib/hooks/useTimeAgo.ts +++ b/src/lib/hooks/useTimeAgo.ts @@ -58,12 +58,12 @@ export function dateDiff( if (diffSeconds < NOW) { diff = { value: 0, - unit: 'now' as DateDiff['unit'], + unit: 'now', } } else if (diffSeconds < MINUTE) { diff = { value: diffSeconds, - unit: 'second' as DateDiff['unit'], + unit: 'second', } } else if (diffSeconds < HOUR) { const value = @@ -72,7 +72,7 @@ export function dateDiff( : Math.floor(diffSeconds / MINUTE) diff = { value, - unit: 'minute' as DateDiff['unit'], + unit: 'minute', } } else if (diffSeconds < DAY) { const value = @@ -81,7 +81,7 @@ export function dateDiff( : Math.floor(diffSeconds / HOUR) diff = { value, - unit: 'hour' as DateDiff['unit'], + unit: 'hour', } } else if (diffSeconds < MONTH_30) { const value = @@ -90,7 +90,7 @@ export function dateDiff( : Math.floor(diffSeconds / DAY) diff = { value, - unit: 'day' as DateDiff['unit'], + unit: 'day', } } else { const value = @@ -99,7 +99,7 @@ export function dateDiff( : Math.floor(diffSeconds / MONTH_30) diff = { value, - unit: 'month' as DateDiff['unit'], + unit: 'month', } } diff --git a/src/lib/icons.tsx b/src/lib/icons.tsx index 4bea1ebeff..3d67dd10b2 100644 --- a/src/lib/icons.tsx +++ b/src/lib/icons.tsx @@ -184,47 +184,6 @@ export function RectTallIcon({ ) } -export function ComposeIcon2({ - style, - size, - strokeWidth = 1.5, -}: { - style?: StyleProp - size?: string | number - strokeWidth?: number -}) { - return ( - - - - - - ) -} - export function InfoCircleIcon({ style, size, diff --git a/src/lib/link-meta/link-meta.ts b/src/lib/link-meta/link-meta.ts index 3d9a695f05..c282a0c4c3 100644 --- a/src/lib/link-meta/link-meta.ts +++ b/src/lib/link-meta/link-meta.ts @@ -1,4 +1,4 @@ -import {type BskyAgent} from '@atproto/api' +import {type AppBskyEmbedExternal, type AtpAgent} from '@atproto/api' import {LINK_META_PROXY} from '#/lib/constants' import {getGiphyMetaUri} from '#/lib/strings/embed-player' @@ -22,10 +22,16 @@ export interface LinkMeta { title?: string description?: string image?: string + /** + * The AT-URI of the Atmosphere record representing this external content, if + * it exists. Example: a site.standard.document record. + */ + associatedRefs?: AppBskyEmbedExternal.External['associatedRefs'] + view?: AppBskyEmbedExternal.View } export async function getLinkMeta( - agent: BskyAgent, + agent: AtpAgent, url: string, timeout = 15e3, ): Promise { @@ -89,6 +95,8 @@ export async function getLinkMeta( meta.description = body.description meta.image = body.image meta.title = body.title + meta.associatedRefs = body.associated_refs + meta.view = body.view || body.external_view if (shouldFollowRedirect) { meta.url = body.url } diff --git a/src/lib/media/manip.ts b/src/lib/media/manip.ts index 2be799e261..84ab65af92 100644 --- a/src/lib/media/manip.ts +++ b/src/lib/media/manip.ts @@ -16,24 +16,21 @@ import {manipulateAsync, SaveFormat} from 'expo-image-manipulator' import * as MediaLibrary from 'expo-media-library' import * as Sharing from 'expo-sharing' -import {POST_IMG_MAX} from '#/lib/constants' import {logger} from '#/logger' import {IS_ANDROID, IS_IOS} from '#/env' import {type PickerImage} from './picker.shared' import {type Dimensions} from './types' -import {convertCdnPreset} from './util' +import {convertCdnPreset, getResizedDimensions} from './util' export async function compressIfNeeded( img: PickerImage, - maxSize: number = POST_IMG_MAX.size, + {maxDimension, maxSize}: {maxDimension: number; maxSize: number}, ): Promise { if (img.size < maxSize) { return img } const resizedImage = await doResize(normalizePath(img.path), { - width: img.width, - height: img.height, - mode: 'stretch', + maxDimension, maxSize, }) const finalImageMovedPath = await moveToPermanentPath( @@ -49,9 +46,7 @@ export async function compressIfNeeded( export interface DownloadAndResizeOpts { uri: string - width: number - height: number - mode: 'contain' | 'cover' | 'stretch' + maxDimension: number maxSize: number timeout: number } @@ -67,7 +62,10 @@ export async function downloadAndResize(opts: DownloadAndResizeOpts) { const path = await downloadImage(opts.uri, String(uuid.v4()), opts.timeout) try { - return await doResize(path, opts) + return await doResize(path, { + maxDimension: opts.maxDimension, + maxSize: opts.maxSize, + }) } finally { void safeDeleteAsync(path) } @@ -188,9 +186,7 @@ export function getImageDim(path: string): Promise { // = interface DoResizeOpts { - width: number - height: number - mode: 'contain' | 'cover' | 'stretch' + maxDimension: number maxSize: number } @@ -204,10 +200,13 @@ async function doResize( // Performing an "empty" manipulation lets us get the dimensions of the original image. React Native's Image.getSize() // does not work for local files... const imageRes = await manipulateAsync(localUri, [], {}) - const newDimensions = getResizedDimensions({ - width: imageRes.width, - height: imageRes.height, - }) + const newDimensions = getResizedDimensions( + { + width: imageRes.width, + height: imageRes.height, + }, + opts.maxDimension, + ) let minQualityPercentage = 0 let maxQualityPercentage = 101 // exclusive @@ -388,28 +387,6 @@ async function withTempFile( } } -export function getResizedDimensions(originalDims: { - width: number - height: number -}) { - if ( - originalDims.width <= POST_IMG_MAX.width && - originalDims.height <= POST_IMG_MAX.height - ) { - return originalDims - } - - const ratio = Math.min( - POST_IMG_MAX.width / originalDims.width, - POST_IMG_MAX.height / originalDims.height, - ) - - return { - width: Math.round(originalDims.width * ratio), - height: Math.round(originalDims.height * ratio), - } -} - async function downloadImage(uri: string, destName: string, timeout: number) { // Download to a temp path first, then rename with the correct extension // based on the response's mimeType. diff --git a/src/lib/media/manip.web.ts b/src/lib/media/manip.web.ts index 8fa2b6a4fa..94732687ef 100644 --- a/src/lib/media/manip.web.ts +++ b/src/lib/media/manip.web.ts @@ -1,27 +1,28 @@ import {type PickerImage} from './picker.shared' import {type Dimensions} from './types' -import {blobToDataUri, convertCdnPreset, getDataUriSize} from './util' +import { + blobToDataUri, + convertCdnPreset, + getDataUriSize, + getResizedDimensions, +} from './util' export async function compressIfNeeded( img: PickerImage, - maxSize: number, + {maxDimension, maxSize}: {maxDimension: number; maxSize: number}, ): Promise { if (img.size < maxSize) { return img } return await doResize(img.path, { - width: img.width, - height: img.height, - mode: 'stretch', + maxDimension, maxSize, }) } export interface DownloadAndResizeOpts { uri: string - width: number - height: number - mode: 'contain' | 'cover' | 'stretch' + maxDimension: number maxSize: number timeout: number } @@ -34,7 +35,10 @@ export async function downloadAndResize(opts: DownloadAndResizeOpts) { clearTimeout(to) const dataUri = await blobToDataUri(resBody) - return await doResize(dataUri, opts) + return await doResize(dataUri, { + maxDimension: opts.maxDimension, + maxSize: opts.maxSize, + }) } export async function shareImageModal(_opts: {uri: string}) { @@ -70,9 +74,7 @@ export async function getImageDim(path: string): Promise { // = interface DoResizeOpts { - width: number - height: number - mode: 'contain' | 'cover' | 'stretch' + maxDimension: number maxSize: number } @@ -80,6 +82,9 @@ async function doResize( dataUri: string, opts: DoResizeOpts, ): Promise { + const sourceDims = await getImageDim(dataUri) + const newDimensions = getResizedDimensions(sourceDims, opts.maxDimension) + let newDataUri let minQualityPercentage = 0 @@ -90,10 +95,10 @@ async function doResize( (maxQualityPercentage + minQualityPercentage) / 2, ) const tempDataUri = await createResizedImage(dataUri, { - width: opts.width, - height: opts.height, + width: newDimensions.width, + height: newDimensions.height, quality: qualityPercentage / 100, - mode: opts.mode, + mode: 'contain', }) if (getDataUriSize(tempDataUri) < opts.maxSize) { @@ -111,8 +116,8 @@ async function doResize( path: newDataUri, mime: 'image/jpeg', size: getDataUriSize(newDataUri), - width: opts.width, - height: opts.height, + width: newDimensions.width, + height: newDimensions.height, } } diff --git a/src/lib/media/picker.e2e.tsx b/src/lib/media/picker.e2e.tsx index f01217d2a5..7aaa69c47d 100644 --- a/src/lib/media/picker.e2e.tsx +++ b/src/lib/media/picker.e2e.tsx @@ -8,6 +8,7 @@ import ExpoImageCropTool, { type OpenCropperOptions, } from '@bsky.app/expo-image-crop-tool' +import {IMAGE_SIZE_CONFIG_2K_1MB} from '#/lib/constants' import {compressIfNeeded} from './manip' import {type PickerImage} from './picker.shared' @@ -28,13 +29,16 @@ async function getFile() { throw new Error('Failed to get file info') } - return await compressIfNeeded({ - path: file, - mime: 'image/jpeg', - size: fileInfo.size, - width: 4288, - height: 2848, - }) + return await compressIfNeeded( + { + path: file, + mime: 'image/jpeg', + size: fileInfo.size, + width: 4288, + height: 2848, + }, + IMAGE_SIZE_CONFIG_2K_1MB, + ) } export async function openPicker(): Promise { diff --git a/src/lib/media/picker.shared.ts b/src/lib/media/picker.shared.ts index 270910e851..d7d8dab440 100644 --- a/src/lib/media/picker.shared.ts +++ b/src/lib/media/picker.shared.ts @@ -2,6 +2,7 @@ import { type ImagePickerOptions, launchImageLibraryAsync, UIImagePickerPreferredAssetRepresentationMode, + VideoExportPreset, } from 'expo-image-picker' import {t} from '@lingui/core/macro' @@ -59,6 +60,7 @@ export async function openUnifiedPicker({ selectionLimit: IS_IOS ? selectionCountRemaining : undefined, preferredAssetRepresentationMode: UIImagePickerPreferredAssetRepresentationMode.Automatic, + videoExportPreset: VideoExportPreset.Passthrough, videoMaxDuration: VIDEO_MAX_DURATION_MS / 1000, }) } diff --git a/src/lib/media/util.ts b/src/lib/media/util.ts index da0b306d4f..8db418f045 100644 --- a/src/lib/media/util.ts +++ b/src/lib/media/util.ts @@ -2,6 +2,31 @@ export function extractDataUriMime(uri: string): string { return uri.substring(uri.indexOf(':') + 1, uri.indexOf(';')) } +export function getResizedDimensions( + originalDims: { + width: number + height: number + }, + maxDimension: number, +) { + if ( + originalDims.width <= maxDimension && + originalDims.height <= maxDimension + ) { + return originalDims + } + + const ratio = Math.min( + maxDimension / originalDims.width, + maxDimension / originalDims.height, + ) + + return { + width: Math.round(originalDims.width * ratio), + height: Math.round(originalDims.height * ratio), + } +} + // Fairly accurate estimate that is more performant // than decoding and checking length of URI export function getDataUriSize(uri: string): number { diff --git a/src/lib/media/video/errors.ts b/src/lib/media/video/errors.ts index 945f89cdda..cbf3083d1f 100644 --- a/src/lib/media/video/errors.ts +++ b/src/lib/media/video/errors.ts @@ -1,6 +1,6 @@ export class VideoTooLargeError extends Error { constructor() { - super('Videos cannot be larger than 100 MB') + super('Videos cannot be larger than 300 MB') this.name = 'VideoTooLargeError' } } diff --git a/src/lib/media/video/upload.shared.ts b/src/lib/media/video/upload.shared.ts index fd46e27868..f8aaa1249b 100644 --- a/src/lib/media/video/upload.shared.ts +++ b/src/lib/media/video/upload.shared.ts @@ -1,4 +1,4 @@ -import {type BskyAgent} from '@atproto/api' +import {type AtpAgent} from '@atproto/api' import {type I18n} from '@lingui/core' import {msg} from '@lingui/core/macro' @@ -13,7 +13,7 @@ export async function getServiceAuthToken({ lxm, exp, }: { - agent: BskyAgent + agent: AtpAgent aud?: string lxm: string exp?: number @@ -30,7 +30,7 @@ export async function getServiceAuthToken({ return serviceAuth.token } -export async function getVideoUploadLimits(agent: BskyAgent, i18n: I18n) { +export async function getVideoUploadLimits(agent: AtpAgent, i18n: I18n) { const token = await getServiceAuthToken({ agent, lxm: 'app.bsky.video.getUploadLimits', diff --git a/src/lib/media/video/upload.ts b/src/lib/media/video/upload.ts index 503577a76a..721ee7f94f 100644 --- a/src/lib/media/video/upload.ts +++ b/src/lib/media/video/upload.ts @@ -1,5 +1,5 @@ import {createUploadTask, FileSystemUploadType} from 'expo-file-system/legacy' -import {type AppBskyVideoDefs, type BskyAgent} from '@atproto/api' +import {type AppBskyVideoDefs, type AtpAgent} from '@atproto/api' import {type I18n} from '@lingui/core' import {msg} from '@lingui/core/macro' import {nanoid} from 'nanoid/non-secure' @@ -19,7 +19,7 @@ export async function uploadVideo({ i18n, }: { video: CompressedVideo - agent: BskyAgent + agent: AtpAgent did: string setProgress: (progress: number) => void signal: AbortSignal diff --git a/src/lib/media/video/upload.web.ts b/src/lib/media/video/upload.web.ts index 98d329a709..e88a04707c 100644 --- a/src/lib/media/video/upload.web.ts +++ b/src/lib/media/video/upload.web.ts @@ -1,4 +1,4 @@ -import {type AppBskyVideoDefs, type BskyAgent} from '@atproto/api' +import {type AppBskyVideoDefs, type AtpAgent} from '@atproto/api' import {type I18n} from '@lingui/core' import {msg} from '@lingui/core/macro' import {nanoid} from 'nanoid/non-secure' @@ -18,7 +18,7 @@ export async function uploadVideo({ i18n, }: { video: CompressedVideo - agent: BskyAgent + agent: AtpAgent did: string setProgress: (progress: number) => void signal: AbortSignal diff --git a/src/lib/moderation.ts b/src/lib/moderation.ts index 9b0ac0ea5f..2fb37009e2 100644 --- a/src/lib/moderation.ts +++ b/src/lib/moderation.ts @@ -1,7 +1,7 @@ import {useMemo} from 'react' import { type AppBskyLabelerDefs, - BskyAgent, + AtpAgent, type ComAtprotoLabelDefs, type InterpretedLabelValueDefinition, LABELS, @@ -91,9 +91,9 @@ export function isAppLabeler( | AppBskyLabelerDefs.LabelerViewDetailed, ): boolean { if (typeof labeler === 'string') { - return BskyAgent.appLabelers.includes(labeler) + return AtpAgent.appLabelers.includes(labeler) } - return BskyAgent.appLabelers.includes(labeler.creator.did) + return AtpAgent.appLabelers.includes(labeler.creator.did) } export function isLabelerSubscribed( diff --git a/src/lib/moderation/blocked-and-muted.ts b/src/lib/moderation/blocked-and-muted.ts index 9806ca83ad..c8cc8611ce 100644 --- a/src/lib/moderation/blocked-and-muted.ts +++ b/src/lib/moderation/blocked-and-muted.ts @@ -1,7 +1,7 @@ import type * as bsky from '#/types/bsky' export function isBlockedOrBlocking(profile: bsky.profile.AnyProfileView) { - return profile.viewer?.blockedBy || profile.viewer?.blocking + return Boolean(profile.viewer?.blockedBy || profile.viewer?.blocking) } export function isMuted(profile: bsky.profile.AnyProfileView) { diff --git a/src/lib/routes/links.ts b/src/lib/routes/links.ts index 908db7cba7..c87ccb5a39 100644 --- a/src/lib/routes/links.ts +++ b/src/lib/routes/links.ts @@ -19,8 +19,8 @@ export function makeProfileLink( export function makeCustomFeedLink( did: string, rkey: string, - segment?: string | undefined, - feedCacheKey?: 'discover' | 'explore' | undefined, + segment?: string, + feedCacheKey?: 'discover' | 'explore', ) { return ( [`/profile`, did, 'feed', rkey, ...(segment ? [segment] : [])].join('/') + diff --git a/src/lib/routes/tab-to-nav-item.ts b/src/lib/routes/tab-to-nav-item.ts new file mode 100644 index 0000000000..38d58b25a4 --- /dev/null +++ b/src/lib/routes/tab-to-nav-item.ts @@ -0,0 +1,19 @@ +import {type Events} from '#/analytics/metrics/types' + +export type SharedNavTab = + | 'Home' + | 'Search' + | 'Messages' + | 'Notifications' + | 'MyProfile' + +export const TAB_TO_NAV_ITEM: Record< + SharedNavTab, + Events['nav:click']['item'] +> = { + Home: 'home', + Search: 'search', + Messages: 'chat', + Notifications: 'notifications', + MyProfile: 'profile', +} diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts index e87aad55c3..8996d2fed3 100644 --- a/src/lib/routes/types.ts +++ b/src/lib/routes/types.ts @@ -55,25 +55,17 @@ export type CommonNavigatorParams = { ActivityPrivacySettings: undefined ContentAndMediaSettings: undefined NotificationSettings: undefined - ReplyNotificationSettings: undefined - MentionNotificationSettings: undefined - QuoteNotificationSettings: undefined - LikeNotificationSettings: undefined - RepostNotificationSettings: undefined - NewFollowerNotificationSettings: undefined - LikesOnRepostsNotificationSettings: undefined - RepostsOnRepostsNotificationSettings: undefined - ActivityNotificationSettings: undefined - MiscellaneousNotificationSettings: undefined InterestsSettings: undefined AboutSettings: undefined AppIconSettings: undefined FindContactsSettings: undefined + InviteScanner: undefined Search: {q?: string; tab?: 'user' | 'profile' | 'feed'} Hashtag: {tag: string; author?: string} Topic: {topic: string} MessagesConversation: {conversation: string; embed?: string; accept?: true} MessagesConversationSettings: {conversation: string} + MessagesJoinRequests: {conversation: string} MessagesSettings: undefined MessagesInbox: undefined NotificationsActivityList: {posts: string} @@ -118,7 +110,11 @@ export type MyProfileTabNavigatorParams = CommonNavigatorParams & { } export type MessagesTabNavigatorParams = CommonNavigatorParams & { - Messages: {pushToConversation?: string; animation?: 'push' | 'pop'} + Messages: { + pushToConversation?: string + pushToNewGroupChat?: boolean + animation?: 'push' | 'pop' + } } export type FlatNavigatorParams = CommonNavigatorParams & { @@ -126,7 +122,11 @@ export type FlatNavigatorParams = CommonNavigatorParams & { Search: {q?: string; tab?: 'user' | 'profile' | 'feed'} Feeds: undefined Notifications: undefined - Messages: {pushToConversation?: string; animation?: 'push' | 'pop'} + Messages: { + pushToConversation?: string + pushToNewGroupChat?: boolean + animation?: 'push' | 'pop' + } } export type AllNavigatorParams = CommonNavigatorParams & { @@ -139,7 +139,11 @@ export type AllNavigatorParams = CommonNavigatorParams & { Notifications: undefined MyProfileTab: undefined MessagesTab: undefined - Messages: {animation?: 'push' | 'pop'} + Messages: { + pushToConversation?: string + pushToNewGroupChat?: boolean + animation?: 'push' | 'pop' + } } // NOTE diff --git a/src/lib/strings/embed-player.ts b/src/lib/strings/embed-player.ts index 446affd87e..f1014c4dcb 100644 --- a/src/lib/strings/embed-player.ts +++ b/src/lib/strings/embed-player.ts @@ -1,6 +1,6 @@ import {Dimensions} from 'react-native' -import {IS_WEB, IS_WEB_SAFARI} from '#/env' +import {IS_WEB} from '#/env' const {height: SCREEN_HEIGHT} = Dimensions.get('window') @@ -65,9 +65,25 @@ export const externalEmbedLabels: Record = { bandcamp: 'Bandcamp', } +/** + * These sources are proxied through our CDN and do not leak any user data, + * thus are safe to embed without a warning. + */ +export const exemptExternalEmbedSources = new Set([ + 'tenor', + 'klipy', +]) + export interface EmbedPlayerParams { type: EmbedPlayerType playerUri: string + /** + * Web-only ordered list of `` tags for `