diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8352de555..8bad3e3cc 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,6 +6,11 @@ updates: schedule: interval: daily open-pull-requests-limit: 10 + cooldown: + default-days: 7 + semver-major-days: 30 + semver-minor-days: 14 + semver-patch-days: 7 ignore: - dependency-name: "*" update-types: @@ -33,6 +38,11 @@ updates: schedule: interval: daily open-pull-requests-limit: 10 + cooldown: + default-days: 7 + semver-major-days: 30 + semver-minor-days: 14 + semver-patch-days: 7 ignore: - dependency-name: "*" update-types: @@ -44,6 +54,8 @@ updates: directory: "/docker/backend" schedule: interval: weekly + cooldown: + default-days: 7 ignore: - dependency-name: "*" update-types: @@ -53,6 +65,8 @@ updates: directory: "/docker/frontend" schedule: interval: weekly + cooldown: + default-days: 7 ignore: - dependency-name: "*" update-types: @@ -62,6 +76,8 @@ updates: directory: "/" schedule: interval: weekly + cooldown: + default-days: 7 ignore: - dependency-name: "*" update-types: diff --git a/.github/nginx-check/Dockerfile.docker b/.github/nginx-check/Dockerfile.docker new file mode 100644 index 000000000..68776f088 --- /dev/null +++ b/.github/nginx-check/Dockerfile.docker @@ -0,0 +1,17 @@ +FROM nginx:1.30.1-alpine + +# Validate the config set loaded by the mempool/frontend docker image. +# Build context: repo root +COPY nginx.conf http-basic.conf /etc/nginx/ +COPY nginx-mempool.conf /etc/nginx/conf.d/ + +# Replay the transforms applied by docker/init.sh (with the default values +# docker/frontend/entrypoint.sh substitutes at container start) +RUN sed -i \ + -e "s!127.0.0.1:80!0.0.0.0:8080!g" \ + -e "s!127.0.0.1!0.0.0.0!g" \ + -e "s!user nobody;!!g" \ + -e "s!/etc/nginx/nginx-mempool.conf!/etc/nginx/conf.d/nginx-mempool.conf!g" \ + /etc/nginx/nginx.conf + +CMD ["nginx", "-t"] diff --git a/.github/nginx-check/Dockerfile.production b/.github/nginx-check/Dockerfile.production new file mode 100644 index 000000000..cb91c2fc6 --- /dev/null +++ b/.github/nginx-check/Dockerfile.production @@ -0,0 +1,13 @@ +FROM nginx:1.30.1-alpine + +# Stage the config the way production/install does: the repo is available at +# ${NGINX_ETC_FOLDER}/mempool and production/nginx/nginx.conf becomes the main +# config, with __NGINX_USER__ and __NGINX_ETC_FOLDER__ substituted. +# Build context: production/ +COPY nginx /etc/nginx/mempool/production/nginx + +RUN cp /etc/nginx/mempool/production/nginx/nginx.conf /etc/nginx/nginx.conf && \ + sed -i "s!__NGINX_USER__!nginx!" /etc/nginx/nginx.conf && \ + sed -i "s!__NGINX_ETC_FOLDER__!/etc/nginx!" /etc/nginx/nginx.conf + +CMD ["nginx", "-t"] diff --git a/.github/workflows/backend-integration.yml b/.github/workflows/backend-integration.yml index 61e55d3c7..43f76b3c1 100644 --- a/.github/workflows/backend-integration.yml +++ b/.github/workflows/backend-integration.yml @@ -2,30 +2,33 @@ name: Backend Integration Tests with MariaDB on: pull_request: - types: [opened, review_requested, synchronize] + types: [opened, synchronize] push: branches: - master +permissions: + contents: read + jobs: backend-integration: if: "(github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')) || github.event_name == 'push'" strategy: matrix: - node: ["22.14.0"] + node: ["24.13.0"] fail-fast: false - runs-on: ubuntu-latest + runs-on: mempool-ci name: Backend Integration Tests - node ${{ matrix.node }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 with: path: ${{ matrix.node }}/integration - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # v3 with: node-version: ${{ matrix.node }} registry-url: "https://registry.npmjs.org" @@ -33,7 +36,7 @@ jobs: cache-dependency-path: '${{ matrix.node }}/integration/backend/package-lock.json' - name: Cache node modules - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: ${{ matrix.node }}/integration/backend/node_modules key: ${{ runner.os }}-backend-integration-node-${{ matrix.node }}-${{ hashFiles('${{ matrix.node }}/integration/backend/package-lock.json') }} @@ -47,7 +50,7 @@ jobs: working-directory: ${{ matrix.node }}/integration - name: Cache Rust dependencies - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | ~/.cargo/bin/ @@ -61,12 +64,12 @@ jobs: ${{ runner.os }}-cargo- - name: Install ${{ steps.gettoolchain.outputs.toolchain }} Rust toolchain - uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 + uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 with: toolchain: ${{ steps.gettoolchain.outputs.toolchain }} - name: Install dependencies - run: npm ci + run: bash meta/scripts/check-install-scripts.sh && npm ci working-directory: ${{ matrix.node }}/integration/backend - name: Build backend diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd6611652..cb1e247ff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,38 +2,44 @@ name: CI Pipeline for the Backend and Frontend on: pull_request: - types: [opened, review_requested, synchronize] + types: [opened, synchronize] push: branches: - master +permissions: + contents: read + jobs: backend: if: "(github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')) || github.event_name == 'push'" strategy: matrix: - node: ["22.14.0"] + node: ["24.13.0"] flavor: ["dev", "prod"] fail-fast: false - runs-on: ubuntu-latest + runs-on: mempool-ci name: Backend (${{ matrix.flavor }}) - node ${{ matrix.node }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 with: path: ${{ matrix.node }}/${{ matrix.flavor }} - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: ${{ matrix.node }} registry-url: "https://registry.npmjs.org" cache: 'npm' cache-dependency-path: '${{ matrix.node }}/${{ matrix.flavor }}/backend/package-lock.json' + - name: Install npm 11.12.0 + run: npm install -g npm@11.12.0 + - name: Cache node modules - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: ${{ matrix.node }}/${{ matrix.flavor }}/backend/node_modules key: ${{ runner.os }}-backend-${{ matrix.flavor }}-node-${{ matrix.node }}-${{ hashFiles('${{ matrix.node }}/${{ matrix.flavor }}/backend/package-lock.json') }} @@ -47,7 +53,7 @@ jobs: working-directory: ${{ matrix.node }}/${{ matrix.flavor }} - name: Cache Rust dependencies - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | ~/.cargo/bin/ @@ -63,18 +69,18 @@ jobs: - name: Install ${{ steps.gettoolchain.outputs.toolchain }} Rust toolchain # Latest version available on this commit is 1.71.1 # Commit date is Aug 3, 2023 - uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 + uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 with: toolchain: ${{ steps.gettoolchain.outputs.toolchain }} - name: Install if: ${{ matrix.flavor == 'dev'}} - run: npm ci + run: bash meta/scripts/check-install-scripts.sh && npm ci working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/backend - name: Install (Prod dependencies only) if: ${{ matrix.flavor == 'prod'}} - run: npm ci --omit=dev --omit=optional + run: bash meta/scripts/check-install-scripts.sh && npm ci --omit=dev --omit=optional working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/backend - name: Lint @@ -96,24 +102,27 @@ jobs: name: "Cache assets for builds" strategy: matrix: - node: ["22.14.0"] - runs-on: ubuntu-latest + node: ["24.13.0"] + runs-on: mempool-ci steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 with: path: assets - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: ${{ matrix.node }} registry-url: "https://registry.npmjs.org" cache: 'npm' cache-dependency-path: 'assets/frontend/package-lock.json' + - name: Install npm 11.12.0 + run: npm install -g npm@11.12.0 + - name: Cache node modules for frontend - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: assets/frontend/node_modules key: ${{ runner.os }}-cache-frontend-node-${{ matrix.node }}-${{ hashFiles('assets/frontend/package-lock.json') }} @@ -122,13 +131,13 @@ jobs: ${{ runner.os }}-cache-frontend- - name: Install (Prod dependencies only) - run: npm ci --omit=dev --omit=optional + run: bash meta/scripts/check-install-scripts.sh && npm ci --omit=dev --omit=optional working-directory: assets/frontend - name: Restore cached mining pool assets continue-on-error: true id: cache-mining-pool-restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | mining-pool-assets.zip @@ -137,7 +146,7 @@ jobs: - name: Restore promo video assets continue-on-error: true id: cache-promo-video-restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | promo-video-assets.zip @@ -170,20 +179,20 @@ jobs: run: zip -jrq promo-video-assets.zip assets/frontend/src/resources/promo-video/* - name: Upload mining pool assets as artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: mining-pool-assets path: mining-pool-assets.zip - name: Upload promo video assets as artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: promo-video-assets path: promo-video-assets.zip - name: Save mining pool assets cache id: cache-mining-pool-save - uses: actions/cache/save@v4 + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | mining-pool-assets.zip @@ -191,7 +200,7 @@ jobs: - name: Save promo video assets cache id: cache-promo-video-save - uses: actions/cache/save@v4 + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | promo-video-assets.zip @@ -202,28 +211,31 @@ jobs: if: "(github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')) || github.event_name == 'push'" strategy: matrix: - node: ["22.14.0"] + node: ["24.13.0"] flavor: ["dev", "prod"] fail-fast: false - runs-on: ubuntu-latest + runs-on: mempool-ci name: Frontend (${{ matrix.flavor }}) - node ${{ matrix.node }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 with: path: ${{ matrix.node }}/${{ matrix.flavor }} - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: ${{ matrix.node }} registry-url: "https://registry.npmjs.org" cache: 'npm' cache-dependency-path: '${{ matrix.node }}/${{ matrix.flavor }}/frontend/package-lock.json' + - name: Install npm 11.12.0 + run: npm install -g npm@11.12.0 + - name: Cache node modules - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: ${{ matrix.node }}/${{ matrix.flavor }}/frontend/node_modules key: ${{ runner.os }}-frontend-${{ matrix.flavor }}-node-${{ matrix.node }}-${{ hashFiles('${{ matrix.node }}/${{ matrix.flavor }}/frontend/package-lock.json') }} @@ -232,13 +244,13 @@ jobs: ${{ runner.os }}-frontend-${{ matrix.flavor }}- - name: Install (Prod dependencies only) - run: npm ci --omit=dev --omit=optional + run: bash meta/scripts/check-install-scripts.sh && npm ci --omit=dev --omit=optional if: ${{ matrix.flavor == 'prod'}} working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/frontend - name: Install if: ${{ matrix.flavor == 'dev'}} - run: npm ci + run: bash meta/scripts/check-install-scripts.sh && npm ci working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/frontend - name: Lint @@ -252,7 +264,7 @@ jobs: - name: Restore cached mining pool assets continue-on-error: true id: cache-mining-pool-restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | mining-pool-assets.zip @@ -261,14 +273,14 @@ jobs: - name: Restore promo video assets continue-on-error: true id: cache-promo-video-restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | promo-video-assets.zip key: promo-video-assets-cache - name: Download artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: mining-pool-assets @@ -276,7 +288,7 @@ jobs: run: unzip -o mining-pool-assets.zip -d ${{ matrix.node }}/${{ matrix.flavor }}/frontend/src/resources/mining-pools - name: Download artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: promo-video-assets @@ -299,40 +311,44 @@ jobs: e2e: if: "(github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')) || github.event_name == 'push'" - runs-on: ubuntu-latest + runs-on: mempool-ci needs: frontend strategy: fail-fast: false matrix: + node: ["24.13.0"] module: ["mempool", "liquid", "testnet4"] name: E2E tests for ${{ matrix.module }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 with: path: ${{ matrix.module }} - name: Setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - node-version: 22 + node-version: ${{ matrix.node }} cache: "npm" cache-dependency-path: ${{ matrix.module }}/frontend/package-lock.json + - name: Install npm 11.12.0 + run: npm install -g npm@11.12.0 + - name: Cache node modules for e2e - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: ${{ matrix.module }}/frontend/node_modules - key: ${{ runner.os }}-e2e-${{ matrix.module }}-node-22-${{ hashFiles('${{ matrix.module }}/frontend/package-lock.json') }} + key: ${{ runner.os }}-e2e-${{ matrix.module }}-node-${{ matrix.node }}-${{ hashFiles('${{ matrix.module }}/frontend/package-lock.json') }} restore-keys: | - ${{ runner.os }}-e2e-${{ matrix.module }}-node-22- + ${{ runner.os }}-e2e-${{ matrix.module }}-node-${{ matrix.node }}- ${{ runner.os }}-e2e-${{ matrix.module }}- - name: Restore cached mining pool assets continue-on-error: true id: cache-mining-pool-restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | mining-pool-assets.zip @@ -341,14 +357,14 @@ jobs: - name: Restore cached promo video assets continue-on-error: true id: cache-promo-video-restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | promo-video-assets.zip key: promo-video-assets-cache - name: Download artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: mining-pool-assets @@ -356,7 +372,7 @@ jobs: run: unzip -o mining-pool-assets.zip -d ${{ matrix.module }}/frontend/src/resources/mining-pools - name: Download artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: promo-video-assets @@ -366,7 +382,7 @@ jobs: # mempool - name: Chrome browser tests (${{ matrix.module }}) if: ${{ matrix.module == 'mempool' }} - uses: cypress-io/github-action@v5 + uses: cypress-io/github-action@248bde77443c376edc45906ede03a1aba9da0462 # v5 with: tag: ${{ github.event_name }} working-directory: ${{ matrix.module }}/frontend @@ -391,7 +407,7 @@ jobs: # liquid - name: Chrome browser tests (${{ matrix.module }}) if: ${{ matrix.module == 'liquid' }} - uses: cypress-io/github-action@v5 + uses: cypress-io/github-action@248bde77443c376edc45906ede03a1aba9da0462 # v5 with: tag: ${{ github.event_name }} working-directory: ${{ matrix.module }}/frontend @@ -416,7 +432,7 @@ jobs: # testnet - name: Chrome browser tests (${{ matrix.module }}) if: ${{ matrix.module == 'testnet4' }} - uses: cypress-io/github-action@v5 + uses: cypress-io/github-action@248bde77443c376edc45906ede03a1aba9da0462 # v5 with: tag: ${{ github.event_name }} working-directory: ${{ matrix.module }}/frontend @@ -439,12 +455,12 @@ jobs: CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }} validate_docker_json: if: "(github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')) || github.event_name == 'push'" - runs-on: ubuntu-latest + runs-on: mempool-ci name: Validate generated backend Docker JSON steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 with: path: docker diff --git a/.github/workflows/dependabot-provenance-check.yml b/.github/workflows/dependabot-provenance-check.yml new file mode 100644 index 000000000..592f18859 --- /dev/null +++ b/.github/workflows/dependabot-provenance-check.yml @@ -0,0 +1,102 @@ +name: Dependabot Provenance Check + +on: + pull_request: + types: [opened, synchronize, reopened] + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + check-provenance: + if: >- + github.event.pull_request.user.login == 'dependabot[bot]' && + !contains(github.event.pull_request.labels.*.name, 'provenance-exception') + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Fetch Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # v2 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + + - name: Setup Node + if: steps.metadata.outputs.package-ecosystem == 'npm_and_yarn' + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: "24.13.0" + + - name: Check npm provenance attestation + if: steps.metadata.outputs.package-ecosystem == 'npm_and_yarn' + id: npm-provenance + run: | + IFS=',' read -ra DEPS <<< "${{ steps.metadata.outputs.dependency-names }}" + UPDATED_DEPS_JSON='${{ steps.metadata.outputs.updated-dependencies-json }}' + + FAILED_DEPS="" + for DEP in "${DEPS[@]}"; do + DEP=$(echo "$DEP" | xargs) + + NEW_VERSION=$(echo "$UPDATED_DEPS_JSON" | jq -r --arg name "$DEP" ' + .[] | select(.dependencyName == $name) | .newVersion + ' | head -n 1) + + if [ -z "$NEW_VERSION" ] || [ "$NEW_VERSION" = "null" ]; then + echo "::error::Could not determine updated version for dependency $DEP from Dependabot metadata" + FAILED_DEPS="${FAILED_DEPS:+$FAILED_DEPS, }$DEP" + continue + fi + echo "::group::Checking provenance for $DEP@$NEW_VERSION" + + ATTESTATIONS=$(npm view "$DEP@$NEW_VERSION" --json 2>/dev/null | jq -r '.dist.attestations // empty') + + if [ -z "$ATTESTATIONS" ]; then + echo "::error::No provenance attestation found for $DEP@$NEW_VERSION" + FAILED_DEPS="${FAILED_DEPS:+$FAILED_DEPS, }$DEP" + else + echo "Provenance attestation found for $DEP@$NEW_VERSION" + fi + echo "::endgroup::" + done + + if [ -n "$FAILED_DEPS" ]; then + echo "failed=true" >> "$GITHUB_OUTPUT" + echo "failed-deps=$FAILED_DEPS" >> "$GITHUB_OUTPUT" + else + echo "failed=false" >> "$GITHUB_OUTPUT" + fi + + - name: Close PR if provenance check failed + if: steps.npm-provenance.outputs.failed == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + FAILED_DEPS: ${{ steps.npm-provenance.outputs.failed-deps }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + COMMENT_BODY_FILE="$(mktemp)" + cat > "$COMMENT_BODY_FILE" <> $GITHUB_ENV + + - name: Set TAG from pushed tag or package.json + run: | + if [ "${{ github.event_name }}" = "push" ]; then + TAG="${GITHUB_REF/refs\/tags\//}" + else + FRONTEND_VERSION=$(jq -r '.version' frontend/package.json) + BACKEND_VERSION=$(jq -r '.version' backend/package.json) + if [ "$FRONTEND_VERSION" != "$BACKEND_VERSION" ]; then + echo "Error: Frontend version ($FRONTEND_VERSION) and backend version ($BACKEND_VERSION) do not match" + exit 1 + fi + TAG="v${FRONTEND_VERSION}-${SHORT_SHA}" + fi + echo "TAG=${TAG}" >> $GITHUB_ENV + + - name: Show set environment variables + run: | + printf " TAG: %s\n" "$TAG" + printf " SHORT_SHA: %s\n" "$SHORT_SHA" + + - name: Init repo for Dockerization + run: docker/init.sh "$TAG" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 + + - name: Build frontend image locally + run: | + docker buildx build \ + --tag test-frontend:$TAG \ + --build-arg commitHash=$SHORT_SHA \ + --load \ + --platform linux/amd64 \ + ./frontend/ + + - name: Build backend image locally + run: | + docker buildx build \ + --tag test-backend:$TAG \ + --build-context rustgbt=./rust \ + --build-context backend=./backend \ + --build-arg commitHash=$SHORT_SHA \ + --load \ + --platform linux/amd64 \ + ./backend/ + + - name: Prepare docker-compose test file + run: | + cat > /tmp/modify_compose.py << 'SCRIPT_END' + import re + import os + import sys + + # Read the base docker-compose file + with open('docker/docker-compose.yml', 'r') as f: + content = f.read() + + # Get TAG from environment + tag = os.environ.get('TAG', '') + + # Replace image names with locally built test images + content = content.replace('image: mempool/frontend:latest', f'image: test-frontend:{tag}') + content = content.replace('image: mempool/backend:latest', f'image: test-backend:{tag}') + + # Change web port mapping from 80:8080 to 8080:8080 + content = content.replace('- 80:8080', '- 8080:8080') + + # Remove volumes from api service + content = re.sub(r' volumes:\n - \.\/data:\/backend\/cache\n', '', content) + + # For db service: remove user and volumes, add tmpfs and healthcheck + # Remove user line from db service (only the one in db service) + lines = content.split('\n') + in_db_service = False + new_lines = [] + for i, line in enumerate(lines): + if line.strip().startswith('db:'): + in_db_service = True + elif line.strip() and not line.startswith(' ') and not line.startswith('\t'): + in_db_service = False + if in_db_service and line.strip() == 'user: "1000:1000"': + continue + new_lines.append(line) + content = '\n'.join(new_lines) + + # Remove volumes section from db service + content = re.sub(r' volumes:\n - \.\/mysql\/data:\/var\/lib\/mysql\n', '', content) + + # Add tmpfs after stop_grace_period in db service (healthcheck already exists in base file) + db_stop_grace = ' stop_grace_period: 1m' + db_additions = ' stop_grace_period: 1m\n tmpfs:\n - /var/lib/mysql' + content = content.replace(db_stop_grace, db_additions, 1) + + # Add depends_on to web service after ports + web_ports = ' ports:\n - 8080:8080' + web_with_depends = ' ports:\n - 8080:8080\n depends_on:\n - api\n - db' + content = content.replace(web_ports, web_with_depends, 1) + + # Add depends_on to api service after command + api_command = ' command: "./wait-for-it.sh db:3306 --timeout=720 --strict -- ./start.sh"' + api_with_depends = ' command: "./wait-for-it.sh db:3306 --timeout=720 --strict -- ./start.sh"\n depends_on:\n - db' + content = content.replace(api_command, api_with_depends, 1) + + # Write the modified content + with open('docker-compose.test.yml', 'w') as f: + f.write(content) + + print("Generated docker-compose.test.yml") + SCRIPT_END + python3 /tmp/modify_compose.py + cat docker-compose.test.yml + + - name: Start containers + run: | + docker compose -f docker-compose.test.yml up -d + + - name: Wait for services to be ready + run: | + echo "Waiting for all services (web, api, db) to be healthy..." + timeout=120 + elapsed=0 + while [ $elapsed -lt $timeout ]; do + # Check health status for all services + PS_OUTPUT=$(docker compose -f docker-compose.test.yml ps) + HEALTHY_COUNT=$(echo "$PS_OUTPUT" | grep -c "(healthy)" || true) + if [ "$HEALTHY_COUNT" -ge 3 ]; then + echo "All services are healthy!" + echo "$PS_OUTPUT" + break + fi + echo "Waiting for services to be healthy... (${elapsed}s/${timeout}s)" + echo "$PS_OUTPUT" + sleep 2 + elapsed=$((elapsed + 2)) + done + if [ $elapsed -ge $timeout ]; then + echo "Services did not become healthy in time" + docker compose -f docker-compose.test.yml ps + docker compose -f docker-compose.test.yml logs + exit 1 + fi + + - name: Verify containers are healthy + run: | + echo "Checking container health status..." + PS_OUTPUT=$(docker compose -f docker-compose.test.yml ps) + echo "$PS_OUTPUT" + + # Check that all three services (web, api, db) are healthy + HEALTHY_COUNT=$(echo "$PS_OUTPUT" | grep -c "(healthy)" || true) + if [ "$HEALTHY_COUNT" -lt 3 ]; then + echo "Not all containers are healthy. Expected 3 healthy services, found $HEALTHY_COUNT" + docker compose -f docker-compose.test.yml logs + exit 1 + fi + + # Verify each service individually for better error messages + if ! echo "$PS_OUTPUT" | grep -q "web.*(healthy)"; then + echo "Web service is not healthy" + docker compose -f docker-compose.test.yml logs web + exit 1 + fi + if ! echo "$PS_OUTPUT" | grep -q "api.*(healthy)"; then + echo "API service is not healthy" + docker compose -f docker-compose.test.yml logs api + exit 1 + fi + if ! echo "$PS_OUTPUT" | grep -q "db.*(healthy)"; then + echo "Database service is not healthy" + docker compose -f docker-compose.test.yml logs db + exit 1 + fi + + echo "All containers are healthy!" + + - name: Show container logs + if: failure() + run: | + docker compose -f docker-compose.test.yml logs + + - name: Clean up containers + if: always() + run: | + docker compose -f docker-compose.test.yml down -v + build: - # Run on tag pushes OR on PRs that have the "docker" label + needs: test-images + # Run on tag pushes OR on PRs with "docker-push" label (after test-images passes) if: | - github.event_name == 'push' || - (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'docker')) + needs.test-images.result == 'success' && + (github.event_name == 'push' || + (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'docker-push'))) strategy: matrix: service: - frontend - backend - runs-on: ubuntu-latest + runs-on: mempool-ci timeout-minutes: 120 name: Build and push to DockerHub outputs: image-digest-frontend: ${{ matrix.service == 'frontend' && steps.docker-build.outputs.digest || '' }} image-digest-backend: ${{ matrix.service == 'backend' && steps.docker-build.outputs.digest || '' }} + tag: ${{ matrix.service == 'frontend' && (steps.set-tag-push.outputs.tag || steps.set-tag-pr.outputs.tag) || '' }} steps: - name: Replace the current swap file shell: bash @@ -65,7 +271,11 @@ jobs: # Only for tag pushes: use the Git tag as TAG - name: Set TAG from pushed tag if: github.event_name == 'push' - run: echo "TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV + id: set-tag-push + run: | + TAG="${GITHUB_REF/refs\/tags\//}" + echo "TAG=${TAG}" >> $GITHUB_ENV + echo "tag=${TAG}" >> $GITHUB_OUTPUT - name: Add SHORT_SHA env property with commit short sha run: | @@ -78,21 +288,27 @@ jobs: - name: Login to Docker for building - run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} - name: Checkout project - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 # For PRs: use package.json version + short sha as TAG - name: Set TAG from service package.json for pull requests if: github.event_name == 'pull_request' + id: set-tag-pr run: | if [ "${{ matrix.service }}" = "frontend" ]; then VERSION=$(jq -r '.version' frontend/package.json) else VERSION=$(jq -r '.version' backend/package.json) fi - echo "TAG=v${VERSION}-${SHORT_SHA}" >> $GITHUB_ENV + TAG="v${VERSION}-${SHORT_SHA}" + echo "TAG=${TAG}" >> $GITHUB_ENV + echo "tag=${TAG}" >> $GITHUB_OUTPUT - name: Show set environment variables run: | @@ -103,13 +319,13 @@ jobs: run: docker/init.sh "$TAG" - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 with: platforms: linux/amd64,linux/arm64 id: qemu - name: Setup Docker buildx action - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 with: platforms: linux/amd64,linux/arm64 driver-opts: | @@ -120,7 +336,7 @@ jobs: run: echo ${{ steps.buildx.outputs.platforms }} - name: Cache Docker layers - uses: actions/cache@v3 + uses: actions/cache@6f8efc29b200d32929f49075959781ed54ec270c # v3 id: cache with: path: /tmp/.buildx-cache @@ -144,9 +360,9 @@ jobs: tag-latest: needs: build - # Only for successful *tag pushes* and only for "plain" versions (no '-') + # Only for successful tag pushes (not PRs with docker-push label) and only for "plain" versions (no '-') if: ${{ needs.build.result == 'success' && github.event_name == 'push' && !contains(github.ref_name, '-') }} - runs-on: ubuntu-latest + runs-on: mempool-ci timeout-minutes: 30 name: Tag release build as latest strategy: @@ -159,19 +375,22 @@ jobs: run: echo "TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 with: platforms: linux/amd64,linux/arm64 - name: Setup Docker buildx action - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 with: platforms: linux/amd64,linux/arm64 driver-opts: | network=host - name: Login to Docker Hub - run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} - name: Tag as latest for ${{ matrix.service }} run: | diff --git a/.github/workflows/e2e_parameterized.yml b/.github/workflows/e2e_parameterized.yml index 8b07ffe82..bf5a09518 100644 --- a/.github/workflows/e2e_parameterized.yml +++ b/.github/workflows/e2e_parameterized.yml @@ -17,12 +17,15 @@ on: description: 'Liquid Hostname' required: true default: 'liquid.network' - type: string + type: string + +permissions: + contents: read jobs: cache: name: "Cache assets for builds" - runs-on: ubuntu-latest + runs-on: mempool-ci steps: - name: Determine checkout ref id: determine-ref @@ -35,25 +38,25 @@ jobs: fi - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 with: ref: ${{ steps.determine-ref.outputs.ref }} path: assets - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # v3 with: - node-version: 22.14.0 + node-version: 24.13.0 registry-url: "https://registry.npmjs.org" - name: Install (Prod dependencies only) - run: npm ci --omit=dev --omit=optional + run: bash meta/scripts/check-install-scripts.sh && npm ci --omit=dev --omit=optional working-directory: assets/frontend - name: Restore cached mining pool assets continue-on-error: true id: cache-mining-pool-restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | mining-pool-assets.zip @@ -62,7 +65,7 @@ jobs: - name: Restore promo video assets continue-on-error: true id: cache-promo-video-restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | promo-video-assets.zip @@ -95,20 +98,20 @@ jobs: run: zip -jrq promo-video-assets.zip assets/frontend/src/resources/promo-video/* - name: Upload mining pool assets as artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: mining-pool-assets path: mining-pool-assets.zip - name: Upload promo video assets as artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: promo-video-assets path: promo-video-assets.zip - name: Save mining pool assets cache id: cache-mining-pool-save - uses: actions/cache/save@v4 + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | mining-pool-assets.zip @@ -116,14 +119,14 @@ jobs: - name: Save promo video assets cache id: cache-promo-video-save - uses: actions/cache/save@v4 + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | promo-video-assets.zip key: promo-video-assets-cache e2e: - runs-on: ubuntu-latest + runs-on: mempool-ci needs: cache strategy: fail-fast: false @@ -143,22 +146,22 @@ jobs: fi - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 with: ref: ${{ steps.determine-ref.outputs.ref }} path: ${{ matrix.module }} - name: Setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # v3 with: - node-version: 22.14.0 + node-version: 24.13.0 cache: "npm" cache-dependency-path: ${{ matrix.module }}/frontend/package-lock.json - name: Restore cached mining pool assets continue-on-error: true id: cache-mining-pool-restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | mining-pool-assets.zip @@ -167,14 +170,14 @@ jobs: - name: Restore cached promo video assets continue-on-error: true id: cache-promo-video-restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | promo-video-assets.zip key: promo-video-assets-cache - name: Download artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: mining-pool-assets @@ -182,7 +185,7 @@ jobs: run: unzip -o mining-pool-assets.zip -d ${{ matrix.module }}/frontend/src/resources/mining-pools - name: Download artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: promo-video-assets @@ -192,7 +195,7 @@ jobs: # mempool - name: Chrome browser tests (${{ matrix.module }}) if: ${{ matrix.module == 'mempool' }} - uses: cypress-io/github-action@v5 + uses: cypress-io/github-action@248bde77443c376edc45906ede03a1aba9da0462 # v5 with: tag: ${{ github.event_name }} working-directory: ${{ matrix.module }}/frontend @@ -220,7 +223,7 @@ jobs: # liquid - name: Chrome browser tests (${{ matrix.module }}) if: ${{ matrix.module == 'liquid' }} - uses: cypress-io/github-action@v5 + uses: cypress-io/github-action@248bde77443c376edc45906ede03a1aba9da0462 # v5 with: tag: ${{ github.event_name }} working-directory: ${{ matrix.module }}/frontend @@ -248,7 +251,7 @@ jobs: # testnet - name: Chrome browser tests (${{ matrix.module }}) if: ${{ matrix.module == 'testnet4' }} - uses: cypress-io/github-action@v5 + uses: cypress-io/github-action@248bde77443c376edc45906ede03a1aba9da0462 # v5 with: tag: ${{ github.event_name }} working-directory: ${{ matrix.module }}/frontend diff --git a/.github/workflows/get_backend_block_height.yml b/.github/workflows/get_backend_block_height.yml index ae30188e5..70d85b20f 100644 --- a/.github/workflows/get_backend_block_height.yml +++ b/.github/workflows/get_backend_block_height.yml @@ -2,13 +2,16 @@ name: 'Check if servers are in sync' on: [workflow_dispatch] +permissions: + contents: read + jobs: print-backend-sha: - runs-on: ubuntu-latest + runs-on: mempool-ci name: Get block height steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 with: path: repo diff --git a/.github/workflows/get_backend_hash.yml b/.github/workflows/get_backend_hash.yml index 0e31735b6..ee0bc6696 100644 --- a/.github/workflows/get_backend_hash.yml +++ b/.github/workflows/get_backend_hash.yml @@ -2,13 +2,16 @@ name: 'Print backend hashes' on: [workflow_dispatch] +permissions: + contents: read + jobs: print-backend-sha: - runs-on: ubuntu-latest + runs-on: mempool-ci name: Print backend hashes steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 with: path: repo diff --git a/.github/workflows/get_image_digest.yml b/.github/workflows/get_image_digest.yml index 18ad39fde..5c03c8d78 100644 --- a/.github/workflows/get_image_digest.yml +++ b/.github/workflows/get_image_digest.yml @@ -7,14 +7,18 @@ on: description: 'Image Version' required: false default: 'latest' - type: string + type: string + +permissions: + contents: read + jobs: print-images-sha: - runs-on: ubuntu-latest + runs-on: mempool-ci name: Print digest for images steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 with: path: digest diff --git a/.github/workflows/project-review-status.yml b/.github/workflows/project-review-status.yml new file mode 100644 index 000000000..83be4de23 --- /dev/null +++ b/.github/workflows/project-review-status.yml @@ -0,0 +1,21 @@ +name: Project Board Automation + +on: + pull_request: + types: [review_requested] + issues: + types: [opened] + +permissions: + contents: read + +jobs: + project-automation: + uses: mempool/.github/.github/workflows/project-board-automation.yml@267e0f38846bfb8cf1b118798dfb12def19ccb35 # master + with: + project-number: 8 + secrets: + PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }} + PROJECT_ID: ${{ secrets.PROJECT_ID }} + STATUS_FIELD_ID: ${{ secrets.STATUS_FIELD_ID }} + REVIEW_NEEDED_OPTION_ID: ${{ secrets.REVIEW_NEEDED_OPTION_ID }} \ No newline at end of file diff --git a/.github/workflows/server-config.yml b/.github/workflows/server-config.yml new file mode 100644 index 000000000..d1a89173c --- /dev/null +++ b/.github/workflows/server-config.yml @@ -0,0 +1,32 @@ +name: Validate server config + +on: + pull_request: + types: [opened, synchronize] + paths: + - "nginx.conf" + - "http-basic.conf" + - "nginx-mempool.conf" + - "production/nginx/**" + - ".github/nginx-check/**" + - ".github/workflows/server-config.yml" + +permissions: + contents: read + +jobs: + check_config: + runs-on: "ubuntu-latest" + + name: Validate nginx config + steps: + - name: Checkout + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 + + - name: Validate the production nginx config + run: | + docker run --rm $(docker build -q -f .github/nginx-check/Dockerfile.production production) + + - name: Validate the docker frontend nginx config + run: | + docker run --rm $(docker build -q -f .github/nginx-check/Dockerfile.docker .) diff --git a/.github/workflows/supply-chain-audit.yml b/.github/workflows/supply-chain-audit.yml new file mode 100644 index 000000000..763e7d468 --- /dev/null +++ b/.github/workflows/supply-chain-audit.yml @@ -0,0 +1,58 @@ +name: Supply Chain Audit + +on: + pull_request: + types: [opened, synchronize] + push: + branches: + - master + +permissions: + contents: read + +jobs: + backend: + if: "(github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')) || github.event_name == 'push'" + runs-on: mempool-ci + name: Backend install-script audit + steps: + - name: Checkout + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 + + - name: Setup Node + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: "24.13.0" + registry-url: "https://registry.npmjs.org" + + - name: Install npm 11.12.0 + run: npm install -g npm@11.12.0 + + - name: Audit backend install scripts + run: bash backend/meta/scripts/check-install-scripts.sh + + - name: Run backend safe install + run: bash backend/meta/scripts/safe-install.sh + + frontend: + if: "(github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')) || github.event_name == 'push'" + runs-on: mempool-ci + name: Frontend install-script audit + steps: + - name: Checkout + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 + + - name: Setup Node + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: "24.13.0" + registry-url: "https://registry.npmjs.org" + + - name: Install npm 11.12.0 + run: npm install -g npm@11.12.0 + + - name: Audit frontend install scripts + run: bash frontend/meta/scripts/check-install-scripts.sh + + - name: Run frontend safe install + run: bash frontend/meta/scripts/safe-install.sh diff --git a/.nvmrc b/.nvmrc index 53d1c14db..e8416a151 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v22 +v24.13.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0e43bda94..0ea569131 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing to The Mempool Open Source Project -Thank you for contributing to The Mempool Open Source Project managed by Mempool Space K.K. (“Mempool”). +Thank you for contributing to The Mempool Open Source Project managed by Mempool Holdings S.A. de C.V. in El Salvador (“Mempool”). In order to clarify the intellectual property license granted with Contributions from any person or entity, Mempool must have a statement on file from each Contributor indicating their agreement to the Contributor License Agreement (“Agreement”). This license is for your protection as a Contributor as well as the protection of Mempool and its other contributors and users; it does not change your rights to use your own Contributions for any other purpose. diff --git a/LICENSE b/LICENSE index cdffcd79b..283935f8c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ The Mempool Open Source Project® -Copyright (c) 2019-2025 Mempool Space K.K. and other shadowy super-coders +Copyright (c) 2019-2026 Mempool Holdings S.A. de C.V. and other shadowy super-coders This program is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -7,17 +7,17 @@ Software Foundation, either version 3 of the License or any later version approved by a proxy statement published on . However, this copyright license does not include an implied right or license -to use any trademarks, service marks, logos, or trade names of Mempool Space K.K. +to use any trademarks, service marks, logos, or trade names of Mempool Holdings or any other contributor to The Mempool Open Source Project. The Mempool Open Source Project®, Mempool Accelerator®, Mempool Enterprise®, Mempool Wallet™, mempool.space®, Be your own explorer™, Explore the full -Bitcoin ecosystem™, Mempool Goggles™, the mempool Logo, the mempool Square Logo, +Bitcoin ecosystem™, Mempool Goggles®, the mempool Logo, the mempool Square Logo, the mempool block visualization Logo, the mempool Blocks Logo, the mempool transaction Logo, the mempool Blocks 3 | 2 Logo, the mempool research Logo, the mempool.space Vertical Logo, and the mempool.space Horizontal Logo are -registered trademarks or trademarks of Mempool Space K.K in Japan, -the United States, and/or other countries. +registered trademarks or trademarks of Mempool Holdings S.A. de C.V. in El +Salvador, Japan, the United States, and/or other countries. See our full Trademark Policy and Guidelines for more details, published on . diff --git a/backend/.eslintignore b/backend/.eslintignore index 76add878f..374800d31 100644 --- a/backend/.eslintignore +++ b/backend/.eslintignore @@ -1,2 +1,4 @@ node_modules -dist \ No newline at end of file +dist +eslint-local-rules +.eslintrc.js \ No newline at end of file diff --git a/backend/.eslintrc b/backend/.eslintrc.js similarity index 64% rename from backend/.eslintrc rename to backend/.eslintrc.js index a9b16ef9d..b53232ee0 100644 --- a/backend/.eslintrc +++ b/backend/.eslintrc.js @@ -1,8 +1,13 @@ -{ +module.exports = { "root": true, "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "./tsconfig.json", + "tsconfigRootDir": __dirname + }, "plugins": [ - "@typescript-eslint" + "@typescript-eslint", + "local-rules" ], "extends": [ "eslint:recommended", @@ -10,6 +15,16 @@ "plugin:@typescript-eslint/recommended", "prettier" ], + "ignorePatterns": ["dist", "eslint-local-rules", ".eslintrc.js", "testSetup*.ts", "jest.integration.*.ts", "__tests__", "__e2e__", "*.config.ts"], + "overrides": [ + { + "files": ["src/__integration_tests__/**/*"], + "rules": { + "@typescript-eslint/no-floating-promises": "off", + "local-rules/no-unhandled-await": "off" + } + } + ], "rules": { "@typescript-eslint/ban-ts-comment": 1, "@typescript-eslint/ban-types": 1, @@ -21,6 +36,8 @@ "@typescript-eslint/no-var-requires": 1, "@typescript-eslint/explicit-function-return-type": 1, "@typescript-eslint/no-unused-vars": 1, + "@typescript-eslint/no-floating-promises": "error", + "local-rules/no-unhandled-await": "error", "no-console": 1, "no-constant-condition": 1, "no-dupe-else-if": 1, diff --git a/backend/.npmrc b/backend/.npmrc new file mode 100644 index 000000000..7253a5cee --- /dev/null +++ b/backend/.npmrc @@ -0,0 +1 @@ +min-release-age=7 diff --git a/backend/docker-compose.test.yml b/backend/docker-compose.test.yml index 491fa3ec2..9cb824d71 100644 --- a/backend/docker-compose.test.yml +++ b/backend/docker-compose.test.yml @@ -8,6 +8,7 @@ services: MYSQL_USER: "mempool_test" MYSQL_PASSWORD: "mempool_test" MYSQL_ROOT_PASSWORD: "admin" + MARIADB_AUTO_UPGRADE: "1" ports: - "33306:3306" healthcheck: diff --git a/backend/eslint-local-rules/index.js b/backend/eslint-local-rules/index.js new file mode 100644 index 000000000..0a4605dd2 --- /dev/null +++ b/backend/eslint-local-rules/index.js @@ -0,0 +1,398 @@ +'use strict'; + +module.exports = { + 'no-unhandled-await': { + meta: { + type: 'problem', + docs: { + description: 'forbid unhandled await unless callee is @asyncSafe, context is @asyncUnsafe, or rejection is explicitly handled', + }, + schema: [{ + type: 'object', + properties: { + safeTag: { type: 'string' }, // jsdoc tag that marks a callee safe (default '@asyncSafe') + unsafeTag: { type: 'string' }, // comment/jsdoc that marks a context unsafe (default '@asyncUnsafe') + allowAllSettled: { type: 'boolean' }, + allowCatchMethod: { type: 'boolean' }, + allowThenWithTwoArgs:{ type: 'boolean' }, + }, + additionalProperties: false, + }], + messages: { + unhandled: + 'await of non-@asyncSafe callee in @asyncSafe context; use try/catch or annotate callee (@asyncSafe) or context (@asyncUnsafe)', + unhandledVoid: + 'void of non-@asyncSafe callee; annotate callee with @asyncSafe or handle the promise properly', + }, + }, + + create(context) { + const src = context.getSourceCode(); + const opt = Object.assign( + { + safeTag: '@asyncSafe', + unsafeTag: '@asyncUnsafe', + allowAllSettled: true, + allowCatchMethod: true, + allowThenWithTwoArgs: true, + }, + (context.options && context.options[0]) || {} + ); + + // optional typescript API (for cross-file/class resolution) + let ts = null, services = null, checker = null, es2ts = null; + try { + // eslint will only populate parserServices if @typescript-eslint/parser + parserOptions.project are set + services = context.parserServices || null; + // @ts-ignore + if (services && (services.program || services.esTreeNodeToTSNodeMap)) { + // eslint-disable-next-line @typescript-eslint/no-var-requires + ts = require('typescript'); + // @ts-ignore + const program = services.program; + // @ts-ignore + es2ts = services.esTreeNodeToTSNodeMap; + checker = program?.getTypeChecker?.(); + } + } catch { /* noop */ } + + const hasRange = (n) => n && Array.isArray(n.range); + const inside = (n, o) => hasRange(n) && hasRange(o) && n.range[0] >= o.range[0] && n.range[1] <= o.range[1]; + const before = (a, b) => hasRange(a) && hasRange(b) && a.range[0] < b.range[0]; + + const isFnNode = (n) => + n && + (n.type === 'FunctionDeclaration' || + n.type === 'FunctionExpression' || + n.type === 'ArrowFunctionExpression' || + n.type === 'MethodDefinition'); + + const isCommentWith = (c, tag) => typeof c?.value === 'string' && c.value.includes(tag); + + // --- jsdoc tag utils ---------------------------------------------------- + const stripAt = (s) => (s || '').replace(/^@/, ''); + + function tsNodeHasJsDocTag(tsNode, tag) { + if (!ts || !tsNode) return false; + try { + const want = stripAt(tag); + const tags = ts.getJSDocTags(tsNode) || []; + return tags.some((t) => { + const n = t.tagName && (t.tagName.escapedText || t.tagName.getText?.()); + return String(n) === want; + }); + } catch { return false; } + } + + function leadingCommentsHaveTag(node, tag) { + if (!node) return false; + const lead = src.getCommentsBefore(node) || []; + return lead.some((c) => isCommentWith(c, tag)); + } + + function isExportWrapper(node) { + return node?.type === 'ExportNamedDeclaration' || node?.type === 'ExportDefaultDeclaration'; + } + + // does a given function *definition* carry tag in its leading comments? + function fnHasTag(fnNode, tag) { + if (!fnNode) return false; + + // 1) method definitions: jsdoc sits on the MethodDefinition + if (fnNode.type === 'MethodDefinition') { + return leadingCommentsHaveTag(fnNode, tag); + } + + // 2) function declarations (also handle `export` wrappers) + if (fnNode.type === 'FunctionDeclaration') { + if (leadingCommentsHaveTag(fnNode, tag)) return true; + const p = fnNode.parent; + if (isExportWrapper(p) && leadingCommentsHaveTag(p, tag)) return true; + const gp = p && p.parent; + if (isExportWrapper(gp) && leadingCommentsHaveTag(gp, tag)) return true; // belt & suspenders + return false; + } + + // 3) function/arrow expressions + if (fnNode.type === 'FunctionExpression' || fnNode.type === 'ArrowFunctionExpression') { + // tag directly on the expression + if (leadingCommentsHaveTag(fnNode, tag)) return true; + + const p = fnNode.parent; + + // tag on class members that wrap the fn expr (class fields or methods-as-values) + if ( + p?.type === 'MethodDefinition' || + p?.type === 'PropertyDefinition' || // ts/estree: class field + p?.type === 'ClassProperty' // older @typescript-eslint + ) { + if (leadingCommentsHaveTag(p, tag)) return true; + } + + // tag on a variable declarator (const fn = async () => {}) + if (p?.type === 'VariableDeclarator') { + if (leadingCommentsHaveTag(p, tag)) return true; + if (p.parent && leadingCommentsHaveTag(p.parent, tag)) return true; // VariableDeclaration + // handle: export const fn = async () => {} + const exp = p.parent && p.parent.parent; + if (isExportWrapper(exp) && leadingCommentsHaveTag(exp, tag)) return true; + } + + // handle: export default (async () => {...}) or export default (async function(){}) + if (isExportWrapper(p) && leadingCommentsHaveTag(p, tag)) return true; + } + + return false; + } + + + // nearest class body ancestor (if any) + function nearestClassBody() { + const anc = context.getAncestors(); + for (let i = anc.length - 1; i >= 0; i--) { + if (anc[i]?.type === 'ClassBody') return anc[i]; + } + return null; + } + + // within the same class, find a method by name + function findMethodInCurrentClass(propertyName) { + const body = nearestClassBody(); + if (!body) return null; + for (const el of body.body || []) { + if (el?.type === 'MethodDefinition') { + // only handle simple identifiers (not computed) rn + if (el.key?.type === 'Identifier' && el.key.name === propertyName) return el; + } + } + return null; + } + + // nearest function ancestor + function nearestFn() { + const anc = context.getAncestors(); + for (let i = anc.length - 1; i >= 0; i--) if (isFnNode(anc[i])) return anc[i]; + return null; + } + + // nearest block or program ancestor + function nearestBlockOrProgram() { + const anc = context.getAncestors(); + for (let i = anc.length - 1; i >= 0; i--) { + const a = anc[i]; + if (a?.type === 'BlockStatement' || a?.type === 'Program') return a; + } + return null; + } + + // context is @asyncUnsafe if the function has the tag OR there is a tagged comment earlier in the same block/program + function contextIsAnnotatedUnsafe(node) { + const fn = nearestFn(); + if (fnHasTag(fn, opt.unsafeTag)) return true; + + const blk = nearestBlockOrProgram(); + if (!blk) return false; + const lead = src.getCommentsBefore(node) || []; + return lead.some((c) => isCommentWith(c, opt.unsafeTag) && inside(c, blk) && before(c, node)); + } + + // in try { ... } ? + function inTryBlock(node) { + const anc = context.getAncestors(); + for (let i = anc.length - 1; i >= 0; i--) { + const a = anc[i]; + if (a?.type === 'TryStatement' && a.block && inside(node, a.block)) return true; + } + return false; + } + + const unwrapChain = (e) => (e && e.type === 'ChainExpression' ? e.expression : e); + + function isHandledAwaitArg(node) { + const arg = unwrapChain(node.argument); + if (!arg) return false; + + // await Promise.allSettled(...) + if ( + opt.allowAllSettled && + arg.type === 'CallExpression' && + arg.callee?.type === 'MemberExpression' && + arg.callee.object?.type === 'Identifier' && + arg.callee.object.name === 'Promise' && + arg.callee.property?.type === 'Identifier' && + arg.callee.property.name === 'allSettled' + ) return true; + + // await p.catch(...) + if ( + opt.allowCatchMethod && + arg.type === 'CallExpression' && + arg.callee?.type === 'MemberExpression' && + arg.callee.property?.type === 'Identifier' && + arg.callee.property.name === 'catch' + ) return true; + + // await p.then(onFulfilled, onRejected) + if ( + opt.allowThenWithTwoArgs && + arg.type === 'CallExpression' && + arg.callee?.type === 'MemberExpression' && + arg.callee.property?.type === 'Identifier' && + arg.callee.property.name === 'then' && + Array.isArray(arg.arguments) && + arg.arguments.length >= 2 + ) return true; + + return false; + } + + // resolve identifier → local function def in scope (best-effort) + function resolveFnFromIdentifier(id) { + const name = id?.name; + if (!name) return null; + let scope = context.getScope(); + while (scope) { + const v = (scope.set && scope.set.get(name)) || scope.variables?.find((vv) => vv.name === name); + if (v && v.defs && v.defs.length) { + for (const d of v.defs) { + const dn = d.node; + if (!dn) continue; + if (dn.type === 'FunctionDeclaration') return dn; + if (dn.type === 'VariableDeclarator') { + const init = dn.init; + if (init && (init.type === 'ArrowFunctionExpression' || init.type === 'FunctionExpression')) { + return init; + } + } + } + } + scope = scope.upper; + } + return null; + } + + // typescript-powered resolution: member call callee → ts declarations → jsdoc tags + function tsMemberIsAnnotatedSafe(memberExpr) { + if (!ts || !checker || !es2ts) return false; + const prop = memberExpr.property; + if (!prop || prop.type !== 'Identifier') return false; // skip computed/strings rn + + try { + const tsObj = es2ts.get(unwrapChain(memberExpr.object)); + if (!tsObj) return false; + let type = checker.getTypeAtLocation(tsObj); + if (!type) return false; + // normalize to apparent type (unions, etc.) + const apparent = checker.getApparentType ? checker.getApparentType(type) : type; + const name = prop.name; + + // ts <5 vs >=5 api differences + const sym = (apparent.getProperty && apparent.getProperty(name)) || + (checker.getPropertyOfType && checker.getPropertyOfType(apparent, name)); + if (!sym || !Array.isArray(sym.declarations)) return false; + + for (const decl of sym.declarations) { + // method, function, property with function type — accept any with @asyncSafe + if (tsNodeHasJsDocTag(decl, opt.safeTag)) return true; + // for class methods, also check the parent (sometimes the tag is on the signature) + if (decl.parent && tsNodeHasJsDocTag(decl.parent, opt.safeTag)) return true; + } + } catch { /* ignore */ } + return false; + } + + // estree-only: this.method() inside same class + function thisMethodIsAnnotatedSafe(memberExpr) { + if (memberExpr.object?.type !== 'ThisExpression') return false; + const prop = memberExpr.property; + if (!prop || prop.type !== 'Identifier') return false; + const m = findMethodInCurrentClass(prop.name); + return fnHasTag(m, opt.safeTag); + } + + function calleeIsAnnotatedSafe(callExpr) { + const arg = unwrapChain(callExpr); + if (!arg) return false; + + if (arg.type === 'CallExpression') { + const c = unwrapChain(arg.callee); + if (!c) return false; + + // direct identifier call + if (c.type === 'Identifier') { + const def = resolveFnFromIdentifier(c); + if (fnHasTag(def, opt.safeTag)) return true; + + // ts fallback for imported funcs + if (ts && checker && es2ts) { + try { + const tsCallee = es2ts.get(c); + const sym = checker.getSymbolAtLocation?.(tsCallee); + const decls = sym?.declarations || []; + for (const d of decls) { + if (tsNodeHasJsDocTag(d, opt.safeTag) || (d.parent && tsNodeHasJsDocTag(d.parent, opt.safeTag))) { + return true; + } + } + } catch { /* noop */ } + } + return false; + } + + // member call: this.m(), obj.m() + if (c.type === 'MemberExpression') { + // 1) easy path: this.method inside same class + if (thisMethodIsAnnotatedSafe(c)) return true; + + // 2) ts-powered cross-file/class/instance resolution + if (tsMemberIsAnnotatedSafe(c)) return true; + + return false; + } + + // function expressions / arrow directly inline + if (c.type === 'FunctionExpression' || c.type === 'ArrowFunctionExpression') { + return fnHasTag(c, opt.safeTag); + } + + // dynamic/new/etc → treat as unsafe + return false; + } + + // awaiting a non-call promise value → treat as unsafe + return false; + } + + // --- main --------------------------------------------------------------- + return { + AwaitExpression(node) { + // handled patterns → ok + if (inTryBlock(node) || isHandledAwaitArg(node)) return; + + // callee carries @asyncSafe? → ok anywhere + if (calleeIsAnnotatedSafe(node.argument)) return; + + // context explicitly @asyncUnsafe? → ok to bubble + if (contextIsAnnotatedUnsafe(node)) return; + + // default: context is safe, callee is unsafe → error + context.report({ node, messageId: 'unhandled' }); + }, + + // void someAsyncCall() — only allowed if callee is @asyncSafe + UnaryExpression(node) { + if (node.operator !== 'void') return; + + const arg = unwrapChain(node.argument); + if (!arg || arg.type !== 'CallExpression') return; + + // callee carries @asyncSafe? → ok + if (calleeIsAnnotatedSafe(node.argument)) return; + + // void of non-safe callee → error + context.report({ node, messageId: 'unhandledVoid' }); + }, + }; + }, + }, +}; diff --git a/backend/jest.config.ts b/backend/jest.config.ts index ae4a6b3b2..78c4ca054 100644 --- a/backend/jest.config.ts +++ b/backend/jest.config.ts @@ -1,24 +1,26 @@ -import type { Config } from "@jest/types" +import type { Config } from '@jest/types'; const config: Config.InitialOptions = { - preset: "ts-jest", - testEnvironment: "node", + preset: 'ts-jest', + testEnvironment: 'node', verbose: true, automock: false, collectCoverage: true, - collectCoverageFrom: ["./src/**/**.ts"], - coverageProvider: "v8", + collectCoverageFrom: ['./src/**/**.ts'], + coverageProvider: 'v8', coverageThreshold: { global: { lines: 1 } }, setupFiles: [ - "./testSetup.ts", + './testSetup.ts', ], testPathIgnorePatterns: [ - "/node_modules/", - "/__integration_tests__/", + '/dist/', + '/node_modules/', + '/__integration_tests__/', + 'test-utils\\.ts$', ], -} +}; export default config; diff --git a/backend/jest.integration.config.ts b/backend/jest.integration.config.ts index f395e94be..f0c159aed 100644 --- a/backend/jest.integration.config.ts +++ b/backend/jest.integration.config.ts @@ -1,21 +1,21 @@ -import type { Config } from "@jest/types" +import type { Config } from '@jest/types'; const config: Config.InitialOptions = { - preset: "ts-jest", - testEnvironment: "node", + preset: 'ts-jest', + testEnvironment: 'node', verbose: true, automock: false, collectCoverage: false, - coverageProvider: "v8", + coverageProvider: 'v8', testMatch: [ - "**/__integration_tests__/**/*.test.ts" + '**/__integration_tests__/**/*.test.ts' ], - globalSetup: "./jest.integration.setup.ts", // Start database before all tests + globalSetup: './jest.integration.setup.ts', // Start database before all tests setupFiles: [ - "./testSetup.integration.ts", + './testSetup.integration.ts', ], - globalTeardown: "./jest.integration.teardown.ts", // Stop database after all tests + globalTeardown: './jest.integration.teardown.ts', // Stop database after all tests maxWorkers: 1, // Force sequential execution -} +}; export default config; diff --git a/backend/jest.integration.setup.ts b/backend/jest.integration.setup.ts index 2156e3cdd..c5093b01a 100644 --- a/backend/jest.integration.setup.ts +++ b/backend/jest.integration.setup.ts @@ -35,18 +35,18 @@ module.exports = async () => { try { const composeFile = path.join(__dirname, 'docker-compose.test.yml'); const dockerComposeCmd = getDockerComposeCmd(); - + // Start the container - execSync(`${dockerComposeCmd} -f "${composeFile}" up -d`, { + execSync(`${dockerComposeCmd} -f "${composeFile}" up -d`, { stdio: 'inherit', cwd: __dirname }); - + // Wait for database to be ready console.log('Waiting for database to be ready...'); let attempts = 0; const maxAttempts = 30; - + while (attempts < maxAttempts) { try { execSync(`${dockerComposeCmd} -f "${composeFile}" exec -T db-test mysqladmin ping -h localhost -u mempool_test -pmempool_test --silent`, { diff --git a/backend/jest.integration.teardown.ts b/backend/jest.integration.teardown.ts index c4229a262..386e26ab3 100644 --- a/backend/jest.integration.teardown.ts +++ b/backend/jest.integration.teardown.ts @@ -43,7 +43,7 @@ module.exports = async () => { ]; await DB.query('SET FOREIGN_KEY_CHECKS = 0'); - + for (const table of tables) { try { // Use 'silent' error logging to avoid noise for optional tables that don't exist @@ -52,29 +52,29 @@ module.exports = async () => { // Table might not exist - silently ignore } } - + await DB.query('SET FOREIGN_KEY_CHECKS = 1'); - + logger.info('Integration tests cleanup completed'); - + // Close the database connection pool to prevent Jest from hanging await DB.close(); logger.info('Database connection pool closed'); - + // Clean up singleton resources that have timers or sockets mempool.destroy(); logger.info('Mempool resources cleaned up'); - + // Close logger's UDP socket last (after all logging is done) logger.close(); - + // Stop and remove the Docker test database container // Skip if SKIP_DB_TEARDOWN is set (e.g., when test-with-db.sh manages the database) if (!process.env.SKIP_DB_TEARDOWN) { try { const composeFile = path.join(__dirname, 'docker-compose.test.yml'); const dockerComposeCmd = getDockerComposeCmd(); - execSync(`${dockerComposeCmd} -f "${composeFile}" down -v`, { + execSync(`${dockerComposeCmd} -f "${composeFile}" down -v`, { stdio: 'inherit', cwd: __dirname }); diff --git a/backend/mempool-config.sample.json b/backend/mempool-config.sample.json index c2715153b..b11616b2a 100644 --- a/backend/mempool-config.sample.json +++ b/backend/mempool-config.sample.json @@ -31,6 +31,8 @@ "AUDIT": false, "RUST_GBT": true, "LIMIT_GBT": false, + "CLUSTER_MEMPOOL": false, + "CLUSTER_MEMPOOL_INDEXING": false, "CPFP_INDEXING": false, "DISK_CACHE_BLOCK_INTERVAL": 6, "MAX_PUSH_TX_SIZE_WEIGHT": 4000000, diff --git a/backend/mempool-config.test.json b/backend/mempool-config.test.json index 2522ca90f..c309099b9 100644 --- a/backend/mempool-config.test.json +++ b/backend/mempool-config.test.json @@ -31,6 +31,8 @@ "AUDIT": false, "RUST_GBT": true, "LIMIT_GBT": false, + "CLUSTER_MEMPOOL": false, + "CLUSTER_MEMPOOL_INDEXING": false, "CPFP_INDEXING": false, "DISK_CACHE_BLOCK_INTERVAL": 6, "MAX_PUSH_TX_SIZE_WEIGHT": 4000000, diff --git a/backend/meta/scripts/check-install-scripts.sh b/backend/meta/scripts/check-install-scripts.sh new file mode 100755 index 000000000..1c7f9baa6 --- /dev/null +++ b/backend/meta/scripts/check-install-scripts.sh @@ -0,0 +1,153 @@ +#!/usr/bin/env bash +# +# Audit backend install-script sources before running a full npm install. +# Exits non-zero if any package not on the whitelist has hasInstallScript: true. +# +# Usage (from repo root): +# backend/meta/scripts/check-install-scripts.sh +# + +set -euo pipefail + +BACKEND_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +LOCKFILE="${BACKEND_DIR}/package-lock.json" +RUST_GBT_DIR="${BACKEND_DIR}/../rust/gbt" +TMP_AUDIT_DIR="" + +BACKEND_ALLOWED=( + "mempool-backend" + "fsevents" + "unrs-resolver" +) + +RUST_GBT_ALLOWED=() + +is_allowed() { + local pkg="$1" + shift + local allowed + for allowed in "$@"; do + if [[ "$pkg" == "$allowed" ]]; then + return 0 + fi + done + return 1 +} + +cleanup() { + if [[ -n "${TMP_AUDIT_DIR}" && -d "${TMP_AUDIT_DIR}" ]]; then + rm -rf "${TMP_AUDIT_DIR}" + fi +} + +trap cleanup EXIT + +collect_has_install_script_packages() { + local lockfile="$1" + if [[ ! -f "$lockfile" ]]; then + echo "No package-lock.json found at ${lockfile}" + return 1 + fi + + LOCKFILE_PATH="$lockfile" node - <<'NODE' +const fs = require('fs'); + +const lock = JSON.parse(fs.readFileSync(process.env.LOCKFILE_PATH, 'utf8')); +const pkgs = lock.packages || {}; + +for (const [path, meta] of Object.entries(pkgs)) { + if (meta && meta.hasInstallScript) { + const name = path === '' ? (lock.name || '(root)') : path.replace(/^.*node_modules\//, ''); + console.log(name); + } +} +NODE +} + +audit_lockfile() { + local lockfile="$1" + local label="$2" + shift 2 + local allowed=("$@") + local found_violations=0 + local pkg + + while IFS= read -r pkg; do + [[ -z "$pkg" ]] && continue + if ! is_allowed "$pkg" "${allowed[@]}"; then + echo "VIOLATION: unauthorized install script in '${pkg}' (${label}: ${lockfile})" + found_violations=1 + fi + done < <(collect_has_install_script_packages "$lockfile") + + if [[ "$found_violations" -eq 1 ]]; then + echo "" + echo "FAILED: Found packages with install scripts not on the whitelist." + return 1 + fi + + echo "OK: All ${label} install scripts are from whitelisted packages." +} + +get_rust_gbt_napi_cli_version() { + local rust_gbt_package_json="${RUST_GBT_DIR}/package.json" + if [[ ! -f "$rust_gbt_package_json" ]]; then + echo "No rust/gbt package.json found at ${rust_gbt_package_json}" + return 1 + fi + + RUST_GBT_PACKAGE_JSON="$rust_gbt_package_json" node - <<'NODE' +const fs = require('fs'); + +const pkg = JSON.parse(fs.readFileSync(process.env.RUST_GBT_PACKAGE_JSON, 'utf8')); +const buildScript = (pkg.scripts && pkg.scripts.build) || ''; +const match = buildScript.match(/npm install --no-save @napi-rs\/cli@([^\s]+)/); + +if (!match) { + console.error('Could not determine the @napi-rs/cli version from rust/gbt/package.json.'); + process.exit(1); +} + +process.stdout.write(match[1]); +NODE +} + +generate_rust_gbt_audit_lockfile() { + local napi_cli_version="$1" + + TMP_AUDIT_DIR="$(mktemp -d "${TMPDIR:-/tmp}/backend-rust-gbt-audit.XXXXXX")" + cat > "${TMP_AUDIT_DIR}/package.json" </dev/null + ) + + printf '%s\n' "${TMP_AUDIT_DIR}/package-lock.json" +} + +audit_rust_gbt_preinstall_chain() { + local napi_cli_version + local rust_gbt_lockfile + + napi_cli_version="$(get_rust_gbt_napi_cli_version)" + rust_gbt_lockfile="$(generate_rust_gbt_audit_lockfile "${napi_cli_version}")" + if [[ "${#RUST_GBT_ALLOWED[@]}" -gt 0 ]]; then + audit_lockfile "${rust_gbt_lockfile}" "backend preinstall toolchain" "${RUST_GBT_ALLOWED[@]}" + else + audit_lockfile "${rust_gbt_lockfile}" "backend preinstall toolchain" + fi +} + +audit_lockfile "${LOCKFILE}" "backend lockfile" "${BACKEND_ALLOWED[@]}" +audit_rust_gbt_preinstall_chain + +echo "OK: Backend install-script audit passed." diff --git a/backend/meta/scripts/safe-install.sh b/backend/meta/scripts/safe-install.sh new file mode 100755 index 000000000..ac5ed7d39 --- /dev/null +++ b/backend/meta/scripts/safe-install.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# +# Safely install backend npm dependencies by refreshing the lockfile without +# running install scripts, auditing it, then doing the real install. +# +# Usage (from repo root): +# backend/meta/scripts/safe-install.sh +# + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BACKEND_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" +LOCKFILE="${BACKEND_DIR}/package-lock.json" +RESTORE_LOCKFILE_DONE=0 + +# Back up the lockfile so we can restore on failure +if [[ -f "$LOCKFILE" ]]; then + cp "$LOCKFILE" "${LOCKFILE}.bak" +fi + +restore_lockfile() { + trap - ERR INT TERM + if [[ "${RESTORE_LOCKFILE_DONE}" -eq 1 ]]; then + return + fi + RESTORE_LOCKFILE_DONE=1 + + if [[ -f "${LOCKFILE}.bak" ]]; then + mv "${LOCKFILE}.bak" "$LOCKFILE" + echo "Restored original package-lock.json." + elif [[ -f "$LOCKFILE" ]]; then + rm "$LOCKFILE" + echo "Removed generated package-lock.json." + fi +} + +trap restore_lockfile ERR INT TERM + +echo "==> Refreshing backend lockfile (--ignore-scripts --package-lock-only)..." +(cd "$BACKEND_DIR" && npm install --ignore-scripts --package-lock-only --no-audit --no-fund) + +echo "" +echo "==> Auditing lockfile for install scripts..." +bash "${SCRIPT_DIR}/check-install-scripts.sh" + +trap - ERR INT TERM +rm -f "${LOCKFILE}.bak" + +echo "" +echo "==> Installing backend (npm ci)..." +(cd "$BACKEND_DIR" && npm ci) + +echo "" +echo "Done." diff --git a/backend/package-lock.json b/backend/package-lock.json index 1cab2025f..532b22092 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1,39 +1,40 @@ { "name": "mempool-backend", - "version": "3.3-dev", + "version": "3.4-dev", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "mempool-backend", - "version": "3.3-dev", + "version": "3.4-dev", "hasInstallScript": true, "license": "GNU Affero General Public License v3.0", "dependencies": { "@mempool/electrum-client": "1.1.9", "@types/node": "^18.15.3", - "axios": "1.12.2", + "axios": "1.16.1", "bitcoinjs-lib": "~6.1.3", "crypto-js": "~4.2.0", - "express": "~4.21.1", + "express": "~4.22.1", "maxmind": "~4.3.11", - "mysql2": "~3.14.1", + "mysql2": "~3.22.4", "redis": "^4.7.0", "rust-gbt": "file:./rust-gbt", "socks-proxy-agent": "~7.0.0", "typescript": "~4.9.3", - "ws": "~8.18.0" + "ws": "~8.21.0" }, "devDependencies": { "@types/compression": "^1.7.2", "@types/crypto-js": "^4.1.1", "@types/express": "^4.17.17", "@types/jest": "^30.0.0", - "@types/ws": "~8.5.10", + "@types/ws": "~8.18.1", "@typescript-eslint/eslint-plugin": "^5.55.0", "@typescript-eslint/parser": "^5.55.0", "eslint": "^8.36.0", "eslint-config-prettier": "^8.8.0", + "eslint-plugin-local-rules": "^3.0.2", "jest": "^30.0.0", "prettier": "^3.0.0", "ts-jest": "^29.4.5", @@ -748,29 +749,6 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1758,9 +1736,9 @@ "license": "MIT" }, "node_modules/@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "dev": true, "dependencies": { "@types/node": "*" @@ -2400,10 +2378,11 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2502,21 +2481,22 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/aws-ssl-profiles": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.1.tgz", - "integrity": "sha512-+H+kuK34PfMaI9PNU/NSjBKL5hh/KDM9J72kwYeYEm0A8B1AC4fuCy3qsjnA7lxklgyXsB68yn8Z2xoZEjgwCQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", "engines": { "node": ">= 6.0.0" } }, "node_modules/axios": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", - "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", + "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" } }, "node_modules/babel-jest": { @@ -2669,22 +2649,23 @@ } }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", "dependencies": { - "bytes": "3.1.2", + "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", + "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" + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", @@ -2699,11 +2680,40 @@ "ms": "2.0.0" } }, + "node_modules/body-parser/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/body-parser/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -2812,28 +2822,11 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/call-bind": { - "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" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -2846,6 +2839,21 @@ "node": ">= 0.4" } }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -3126,22 +3134,6 @@ "node": ">=0.10.0" } }, - "node_modules/define-data-property": { - "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" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -3186,10 +3178,11 @@ } }, "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } @@ -3423,6 +3416,13 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-plugin-local-rules": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-local-rules/-/eslint-plugin-local-rules-3.0.2.tgz", + "integrity": "sha512-IWME7GIYHXogTkFsToLdBCQVJ0U4kbSuVyDT+nKoR4UgtnVrrVeNWuAZkdEu1nxkvi9nsPccGehEEF6dgA28IQ==", + "dev": true, + "license": "MIT" + }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -3647,39 +3647,38 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "license": "MIT", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "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", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "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", + "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", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -3855,15 +3854,16 @@ } }, "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "funding": [ { "type": "individual", @@ -3910,9 +3910,9 @@ } }, "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -4092,17 +4092,40 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/glob/node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -4183,17 +4206,6 @@ "node": ">=8" } }, - "node_modules/has-property-descriptors": { - "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" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -4252,6 +4264,18 @@ "node": ">= 0.8" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -4266,6 +4290,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -5277,9 +5302,9 @@ "dev": true }, "node_modules/long": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", - "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" }, "node_modules/lru-cache": { "version": "5.1.1", @@ -5292,10 +5317,9 @@ } }, "node_modules/lru.min": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.1.tgz", - "integrity": "sha512-FbAj6lXil6t8z4z3j0E5mfRlPzxkySotzUHwRXjlpRh10vc6AI6WN62ehZj82VG7M20rqogJ0GLwar2Xa05a8Q==", - "license": "MIT", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", + "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", "engines": { "bun": ">=1.0.0", "deno": ">=1.30.0", @@ -5467,10 +5491,11 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -5513,52 +5538,50 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/mysql2": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.14.1.tgz", - "integrity": "sha512-7ytuPQJjQB8TNAYX/H2yhL+iQOnIBjAMam361R7UAL0lOVXWjtdrmoL9HYKqKoLp/8UUTRcvo1QPvK9KL7wA8w==", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.22.4.tgz", + "integrity": "sha512-CtXYlmL7ZamiYKbmqkamQHWJROUHSfm+f3kByzGfknw7kW51mcB2ouMUqYq1XfYxbXmnWo6RhPydx6OCqdgcmQ==", "dependencies": { - "aws-ssl-profiles": "^1.1.1", + "aws-ssl-profiles": "^1.1.2", "denque": "^2.1.0", "generate-function": "^2.3.1", - "iconv-lite": "^0.6.3", - "long": "^5.2.1", - "lru.min": "^1.0.0", - "named-placeholders": "^1.1.3", - "seq-queue": "^0.0.5", - "sqlstring": "^2.3.2" + "iconv-lite": "^0.7.2", + "long": "^5.3.2", + "lru.min": "^1.1.4", + "named-placeholders": "^1.1.6", + "sql-escaper": "^1.3.3" }, "engines": { "node": ">= 8.0" + }, + "peerDependencies": { + "@types/node": ">= 8" } }, "node_modules/mysql2/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/named-placeholders": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", - "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", + "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", "dependencies": { - "lru-cache": "^7.14.1" + "lru.min": "^1.1.0" }, "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/named-placeholders/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "engines": { - "node": ">=12" + "node": ">=8.0.0" } }, "node_modules/napi-postinstall": { @@ -5642,9 +5665,9 @@ } }, "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "engines": { "node": ">= 0.4" }, @@ -6005,9 +6028,12 @@ } }, "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "engines": { + "node": ">=10" + } }, "node_modules/punycode": { "version": "2.3.0", @@ -6036,11 +6062,11 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -6078,19 +6104,49 @@ } }, "node_modules/raw-body": { - "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==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" } }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -6287,11 +6343,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "node_modules/seq-queue": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", - "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" - }, "node_modules/serve-static": { "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", @@ -6306,22 +6357,6 @@ "node": ">= 0.8.0" } }, - "node_modules/set-function-length": { - "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" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -6349,14 +6384,65 @@ } }, "node_modules/side-channel": { - "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==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dependencies": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "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" }, "engines": { "node": ">= 0.4" @@ -6437,12 +6523,18 @@ "source-map": "^0.6.0" } }, - "node_modules/sqlstring": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", - "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "node_modules/sql-escaper": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.3.3.tgz", + "integrity": "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw==", "engines": { - "node": ">= 0.6" + "bun": ">=1.0.0", + "deno": ">=2.0.0", + "node": ">=12.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/mysqljs/sql-escaper?sponsor=1" } }, "node_modules/stack-utils": { @@ -7101,9 +7193,9 @@ } }, "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "engines": { "node": ">=10.0.0" }, @@ -7689,21 +7781,6 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, - "@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "dev": true - }, - "@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", - "dev": true, - "requires": { - "@isaacs/balanced-match": "^4.0.1" - } - }, "@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -8481,9 +8558,9 @@ "dev": true }, "@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "dev": true, "requires": { "@types/node": "*" @@ -8860,9 +8937,9 @@ } }, "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -8934,18 +9011,19 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "aws-ssl-profiles": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.1.tgz", - "integrity": "sha512-+H+kuK34PfMaI9PNU/NSjBKL5hh/KDM9J72kwYeYEm0A8B1AC4fuCy3qsjnA7lxklgyXsB68yn8Z2xoZEjgwCQ==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==" }, "axios": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", - "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", + "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", "requires": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" } }, "babel-jest": { @@ -9059,22 +9137,22 @@ } }, "body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", "requires": { - "bytes": "3.1.2", + "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", + "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" + "unpipe": "~1.0.0" }, "dependencies": { "debug": { @@ -9085,10 +9163,27 @@ "ms": "2.0.0" } }, + "http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "requires": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==" } } }, @@ -9170,18 +9265,6 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, - "call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "requires": { - "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-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -9191,6 +9274,15 @@ "function-bind": "^1.1.2" } }, + "call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -9372,16 +9464,6 @@ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true }, - "define-data-property": { - "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==", - "requires": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - } - }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -9409,9 +9491,9 @@ "dev": true }, "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", "dev": true }, "dir-glob": { @@ -9625,6 +9707,12 @@ "dev": true, "requires": {} }, + "eslint-plugin-local-rules": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-local-rules/-/eslint-plugin-local-rules-3.0.2.tgz", + "integrity": "sha512-IWME7GIYHXogTkFsToLdBCQVJ0U4kbSuVyDT+nKoR4UgtnVrrVeNWuAZkdEu1nxkvi9nsPccGehEEF6dgA28IQ==", + "dev": true + }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -9741,38 +9829,38 @@ } }, "express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "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", + "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", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -9921,15 +10009,15 @@ } }, "flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true }, "follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==" + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==" }, "foreground-child": { "version": "3.3.1", @@ -9950,9 +10038,9 @@ } }, "form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -10060,13 +10148,28 @@ "path-scurry": "^2.0.0" }, "dependencies": { - "minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true + }, + "brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", "dev": true, "requires": { - "@isaacs/brace-expansion": "^5.0.0" + "balanced-match": "^4.0.2" + } + }, + "minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "requires": { + "brace-expansion": "^5.0.2" } } } @@ -10130,14 +10233,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "requires": { - "es-define-property": "^1.0.0" - } - }, "has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -10177,6 +10272,15 @@ "toidentifier": "1.0.1" } }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "requires": { + "agent-base": "6", + "debug": "4" + } + }, "human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -10902,9 +11006,9 @@ "dev": true }, "long": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", - "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" }, "lru-cache": { "version": "5.1.1", @@ -10916,9 +11020,9 @@ } }, "lru.min": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.1.tgz", - "integrity": "sha512-FbAj6lXil6t8z4z3j0E5mfRlPzxkySotzUHwRXjlpRh10vc6AI6WN62ehZj82VG7M20rqogJ0GLwar2Xa05a8Q==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", + "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==" }, "make-dir": { "version": "4.0.0", @@ -11028,9 +11132,9 @@ "dev": true }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -11059,25 +11163,24 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "mysql2": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.14.1.tgz", - "integrity": "sha512-7ytuPQJjQB8TNAYX/H2yhL+iQOnIBjAMam361R7UAL0lOVXWjtdrmoL9HYKqKoLp/8UUTRcvo1QPvK9KL7wA8w==", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.22.4.tgz", + "integrity": "sha512-CtXYlmL7ZamiYKbmqkamQHWJROUHSfm+f3kByzGfknw7kW51mcB2ouMUqYq1XfYxbXmnWo6RhPydx6OCqdgcmQ==", "requires": { - "aws-ssl-profiles": "^1.1.1", + "aws-ssl-profiles": "^1.1.2", "denque": "^2.1.0", "generate-function": "^2.3.1", - "iconv-lite": "^0.6.3", - "long": "^5.2.1", - "lru.min": "^1.0.0", - "named-placeholders": "^1.1.3", - "seq-queue": "^0.0.5", - "sqlstring": "^2.3.2" + "iconv-lite": "^0.7.2", + "long": "^5.3.2", + "lru.min": "^1.1.4", + "named-placeholders": "^1.1.6", + "sql-escaper": "^1.3.3" }, "dependencies": { "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "requires": { "safer-buffer": ">= 2.1.2 < 3.0.0" } @@ -11085,18 +11188,11 @@ } }, "named-placeholders": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", - "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", + "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", "requires": { - "lru-cache": "^7.14.1" - }, - "dependencies": { - "lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==" - } + "lru.min": "^1.1.0" } }, "napi-postinstall": { @@ -11156,9 +11252,9 @@ } }, "object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==" + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==" }, "on-finished": { "version": "2.4.1", @@ -11395,9 +11491,9 @@ } }, "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==" }, "punycode": { "version": "2.3.0", @@ -11412,11 +11508,11 @@ "dev": true }, "qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "requires": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" } }, "queue-microtask": { @@ -11431,14 +11527,33 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "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==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "dependencies": { + "http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "requires": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + } + }, + "statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==" + } } }, "react-is": { @@ -11579,11 +11694,6 @@ } } }, - "seq-queue": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", - "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" - }, "serve-static": { "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", @@ -11595,19 +11705,6 @@ "send": "0.19.0" } }, - "set-function-length": { - "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==", - "requires": { - "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": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -11629,14 +11726,47 @@ "dev": true }, "side-channel": { - "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==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "requires": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + } + }, + "side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + } + }, + "side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + } + }, + "side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "requires": { + "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" } }, "signal-exit": { @@ -11691,10 +11821,10 @@ "source-map": "^0.6.0" } }, - "sqlstring": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", - "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==" + "sql-escaper": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.3.3.tgz", + "integrity": "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw==" }, "stack-utils": { "version": "2.0.6", @@ -12104,9 +12234,9 @@ } }, "ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "requires": {} }, "y18n": { diff --git a/backend/package.json b/backend/package.json index 0facd684b..fb9c335e3 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "mempool-backend", - "version": "3.3-dev", + "version": "3.4-dev", "description": "Bitcoin mempool visualizer and blockchain explorer backend", "license": "GNU Affero General Public License v3.0", "homepage": "https://mempool.space", @@ -43,28 +43,29 @@ "dependencies": { "@mempool/electrum-client": "1.1.9", "@types/node": "^18.15.3", - "axios": "1.12.2", + "axios": "1.16.1", "bitcoinjs-lib": "~6.1.3", "crypto-js": "~4.2.0", - "express": "~4.21.1", + "express": "~4.22.1", "maxmind": "~4.3.11", - "mysql2": "~3.14.1", + "mysql2": "~3.22.4", "rust-gbt": "file:./rust-gbt", "redis": "^4.7.0", "socks-proxy-agent": "~7.0.0", "typescript": "~4.9.3", - "ws": "~8.18.0" + "ws": "~8.21.0" }, "devDependencies": { "@types/compression": "^1.7.2", "@types/crypto-js": "^4.1.1", "@types/express": "^4.17.17", "@types/jest": "^30.0.0", - "@types/ws": "~8.5.10", + "@types/ws": "~8.18.1", "@typescript-eslint/eslint-plugin": "^5.55.0", "@typescript-eslint/parser": "^5.55.0", "eslint": "^8.36.0", "eslint-config-prettier": "^8.8.0", + "eslint-plugin-local-rules": "^3.0.2", "jest": "^30.0.0", "prettier": "^3.0.0", "ts-jest": "^29.4.5", diff --git a/backend/src/__e2e__/cluster-mempool/harness/rpc-client.ts b/backend/src/__e2e__/cluster-mempool/harness/rpc-client.ts new file mode 100644 index 000000000..6ef621028 --- /dev/null +++ b/backend/src/__e2e__/cluster-mempool/harness/rpc-client.ts @@ -0,0 +1,137 @@ +import http from 'http'; + +export interface RpcConfig { + host: string; + port: number; + user: string; + pass: string; +} + +interface RpcResponse { + result: any; + error: { code: number; message: string } | null; + id: string; +} + +export class RpcClient { + private config: RpcConfig; + private idCounter = 0; + + constructor(config: RpcConfig) { + this.config = config; + } + + private async call(method: string, params: any[] = []): Promise { + const id = String(++this.idCounter); + const body = JSON.stringify({ jsonrpc: '2.0', id, method, params }); + const auth = Buffer.from(`${this.config.user}:${this.config.pass}`).toString('base64'); + + return new Promise((resolve, reject) => { + const req = http.request( + { + hostname: this.config.host, + port: this.config.port, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Basic ${auth}`, + 'Content-Length': Buffer.byteLength(body), + }, + }, + (res) => { + let data = ''; + res.on('data', (chunk) => (data += chunk)); + res.on('end', () => { + try { + const parsed: RpcResponse = JSON.parse(data); + if (parsed.error) { + reject(new Error(`RPC error ${parsed.error.code}: ${parsed.error.message}`)); + } else { + resolve(parsed.result); + } + } catch (e) { + reject(new Error(`Failed to parse RPC response: ${data.slice(0, 500)}`)); + } + }); + }, + ); + req.on('error', reject); + req.write(body); + req.end(); + }); + } + + async getRawMempool(): Promise { + return this.call('getrawmempool'); + } + + async getRawTransaction(txid: string, verbose = true): Promise { + return this.call('getrawtransaction', [txid, verbose]); + } + + async getBlockTemplate(rules: string[] = ['segwit']): Promise { + return this.call('getblocktemplate', [{ rules }]); + } + + async getMempoolEntry(txid: string): Promise { + return this.call('getmempoolentry', [txid]); + } + + async getMempoolCluster(txid: string): Promise { + return this.call('getmempoolcluster', [txid]); + } + + async getBlockCount(): Promise { + return this.call('getblockcount'); + } + + async batch(calls: { method: string; params: any[] }[]): Promise { + if (calls.length === 0) { + return []; + } + const bodies = calls.map((c) => ({ + jsonrpc: '2.0', + id: String(++this.idCounter), + method: c.method, + params: c.params, + })); + const body = JSON.stringify(bodies); + const auth = Buffer.from(`${this.config.user}:${this.config.pass}`).toString('base64'); + + return new Promise((resolve, reject) => { + const req = http.request( + { + hostname: this.config.host, + port: this.config.port, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Basic ${auth}`, + 'Content-Length': Buffer.byteLength(body), + }, + }, + (res) => { + let data = ''; + res.on('data', (chunk) => (data += chunk)); + res.on('end', () => { + try { + const parsed: RpcResponse[] = JSON.parse(data); + const results = parsed.map((r) => { + if (r.error) { + return { error: r.error }; + } + return r.result; + }); + resolve(results); + } catch (e) { + reject(new Error(`Failed to parse batch RPC response: ${data.slice(0, 500)}`)); + } + }); + }, + ); + req.on('error', reject); + req.write(body); + req.end(); + }); + } +} diff --git a/backend/src/__e2e__/cluster-mempool/harness/run-harness.ts b/backend/src/__e2e__/cluster-mempool/harness/run-harness.ts new file mode 100644 index 000000000..78c19844c --- /dev/null +++ b/backend/src/__e2e__/cluster-mempool/harness/run-harness.ts @@ -0,0 +1,463 @@ +/** + * Cluster Mempool Test Harness + * + * Standalone script that compares our ClusterMempool block template ordering + * against Bitcoin Core's getblocktemplate output. + * + * Uses the backend's own transactionUtils to fetch and convert transactions, + * ensuring fields (sigops, adjustedVsize, etc.) match exactly. + * + * Usage: + * npx ts-node src/__tests__/cluster-mempool/harness/run-harness.ts [options] + * + * Options: + * --host Override CORE_RPC host + * --port Override CORE_RPC port + * --user Override CORE_RPC username + * --pass Override CORE_RPC password + * --interval Comparison interval in ms (default: 30000) + * --poll Mempool poll interval in ms (default: 1000) + * --max Max comparisons before exit (0 = unlimited, default: 0) + */ + +import { RpcClient, RpcConfig } from './rpc-client'; + +// ─── CLI Parsing (must happen before backend imports) ─────────────────────── + +interface CliOptions { + rpcOverrides: Partial; + comparisonInterval: number; + pollInterval: number; + maxComparisons: number; +} + +function parseArgs(): CliOptions { + const args = process.argv.slice(2); + const overrides: Partial = {}; + let comparisonInterval = 30_000; + let pollInterval = 1000; + let maxComparisons = 0; + + for (let i = 0; i < args.length; i++) { + switch (args[i]) { + case '--host': + overrides.host = args[++i]; + break; + case '--port': + overrides.port = parseInt(args[++i], 10); + break; + case '--user': + overrides.user = args[++i]; + break; + case '--pass': + overrides.pass = args[++i]; + break; + case '--interval': + comparisonInterval = parseInt(args[++i], 10); + break; + case '--poll': + pollInterval = parseInt(args[++i], 10); + break; + case '--max': + maxComparisons = parseInt(args[++i], 10); + break; + } + } + + return { rpcOverrides: overrides, comparisonInterval, pollInterval, maxComparisons }; +} + +const cliOptions = parseArgs(); + +// ─── Backend Module Loading ───────────────────────────────────────────────── +// Import config first, apply CLI overrides, then import modules that depend on it. +// This works because Node's require() caches modules — when transactionUtils +// (via bitcoinClient) reads config, it sees our modified values. + +const config = require('../../../config').default; + +if (cliOptions.rpcOverrides.host) { config.CORE_RPC.HOST = cliOptions.rpcOverrides.host; } +if (cliOptions.rpcOverrides.port) { config.CORE_RPC.PORT = cliOptions.rpcOverrides.port; } +if (cliOptions.rpcOverrides.user) { config.CORE_RPC.USERNAME = cliOptions.rpcOverrides.user; } +if (cliOptions.rpcOverrides.pass) { config.CORE_RPC.PASSWORD = cliOptions.rpcOverrides.pass; } + +// Now load modules that depend on config +const bitcoinApi = require('../../../api/bitcoin/bitcoin-api-factory').default; +const transactionUtils = require('../../../api/transaction-utils').default; +const { ClusterMempool } = require('../../../cluster-mempool/cluster-mempool'); + +import { MempoolTransactionExtended } from '../../../mempool.interfaces'; +import { MempoolDiff } from '../../../cluster-mempool/cluster-mempool'; + +// ─── Main Harness ─────────────────────────────────────────────────────────── + +const TX_FETCH_BATCH_SIZE = 1000; + +class Harness { + private rpc: RpcClient; + private clusterMempool: InstanceType | null = null; + private mempool: { [txid: string]: MempoolTransactionExtended } = {}; + private knownTxids = new Set(); + private lastBlockHeight = -1; + private comparisonInterval: number; + private pollInterval: number; + private maxComparisons: number; + private pollTimer: ReturnType | null = null; + private polling = false; + private nextComparisonTime = 0; + private stats = { + templateMatches: 0, + templateMismatches: 0, + comparisons: 0, + }; + + constructor(rpcConfig: RpcConfig, opts: CliOptions) { + this.rpc = new RpcClient(rpcConfig); + this.comparisonInterval = opts.comparisonInterval; + this.pollInterval = opts.pollInterval; + this.maxComparisons = opts.maxComparisons; + } + + async run(): Promise { + console.log('=== Cluster Mempool Test Harness ==='); + console.log(`RPC: ${config.CORE_RPC.HOST}:${config.CORE_RPC.PORT}`); + console.log(`Backend: ${config.MEMPOOL.BACKEND}`); + console.log(`Poll interval: ${this.pollInterval}ms`); + console.log(`Comparison interval: ${this.comparisonInterval}ms`); + console.log(); + + // Step 1: Fetch full mempool + console.log('Fetching full mempool...'); + const txids: string[] = await bitcoinApi.$getRawMempool(); + console.log(`Got ${txids.length} txids from getrawmempool`); + + const fetchStart = Date.now(); + await this.fetchTransactions(txids); + const fetchTime = Date.now() - fetchStart; + console.log(`Fetched ${Object.keys(this.mempool).length} transactions in ${fetchTime}ms`); + + // Step 2: Initialize ClusterMempool + console.log('Building cluster mempool...'); + const buildStart = Date.now(); + this.clusterMempool = new ClusterMempool(this.mempool); + const buildTime = Date.now() - buildStart; + console.log(`Cluster mempool built in ${buildTime}ms`); + console.log(` Clusters: ${this.clusterMempool.getClusterCount()}`); + console.log(` Transactions: ${this.clusterMempool.getTxCount()}`); + console.log(); + + // Record initial block height + this.lastBlockHeight = await this.rpc.getBlockCount(); + + // Run initial comparison, then start polling + await this.runComparison(); + this.nextComparisonTime = Date.now() + this.comparisonInterval; + this.pollTimer = setInterval(() => this.pollMempool(), this.pollInterval); + + console.log('\nHarness running. Press Ctrl+C to stop.\n'); + + process.on('SIGINT', () => this.shutdown()); + process.on('SIGTERM', () => this.shutdown()); + } + + private async fetchTransactions(txids: string[]): Promise { + const added: MempoolTransactionExtended[] = []; + + for (let offset = 0; offset < txids.length; offset += TX_FETCH_BATCH_SIZE) { + let batch = txids.slice(offset, offset + TX_FETCH_BATCH_SIZE); + let txs: MempoolTransactionExtended[] = []; + let tries = 0; + while (batch.length && tries < 20) { + try { + tries++; + txs = txs.concat(await transactionUtils.$getMempoolTransactionsExtended(batch, false, false, false)); + let missing: string[] = []; + console.log(`txs: ${txs.length} of ${batch.length} fetched`); + for (const txid of batch) { + if (!txs.some(tx => tx.txid === txid)) { + console.log(`missing ${txid} at offset ${offset}, retrying`); + missing.push(txid); + } + } + batch = missing; + if (batch.length) { + await new Promise(resolve => setTimeout(resolve, 500)); + } + } catch (err: any) { + console.log(` Fetch batch failed at offset ${offset}: ${err.message}, retrying`); + } + } + + for (const tx of txs) { + this.mempool[tx.txid] = tx; + this.knownTxids.add(tx.txid); + added.push(tx); + } + + if (txids.length > TX_FETCH_BATCH_SIZE && (offset + batch.length) % 5000 < TX_FETCH_BATCH_SIZE) { + console.log(` ${offset + batch.length} / ${txids.length} fetched...`); + } + } + + return added; + } + + private async pollMempool(): Promise { + if (this.polling || !this.clusterMempool) { + return; + } + this.polling = true; + + try { + // Check for new block + const height = await this.rpc.getBlockCount(); + const newBlock = height > this.lastBlockHeight; + if (newBlock) { + console.log(`\n--- New block detected (height ${height}) ---`); + this.lastBlockHeight = height; + } + + // Get current mempool + const currentTxids = await bitcoinApi.$getRawMempool(); + const currentSet = new Set(currentTxids); + + // Find added and removed + const addedTxids: string[] = []; + for (const txid of currentTxids) { + if (!this.knownTxids.has(txid)) { + addedTxids.push(txid); + } + } + const removedTxids: string[] = []; + for (const txid of this.knownTxids) { + if (!currentSet.has(txid)) { + removedTxids.push(txid); + } + } + + if (addedTxids.length === 0 && removedTxids.length === 0) { + if (newBlock) { + await this.runComparison(); + } + return; + } + + const added = await this.fetchTransactions(addedTxids); + + const removed = removedTxids + .map(txid => this.mempool[txid]) + .filter((tx): tx is MempoolTransactionExtended => !!tx); + const diff: MempoolDiff = { added, removed, accelerations: {} }; + const t0 = Date.now(); + this.clusterMempool.applyMempoolChange(diff); + const dt = Date.now() - t0; + for (const txid of removedTxids) { + delete this.mempool[txid]; + this.knownTxids.delete(txid); + } + console.log( + `Applied${newBlock ? ' block' : ''} diff: +${added.length} -${removedTxids.length} txs in ${dt}ms` + + ` (${this.clusterMempool.getTxCount()} txs, ${this.clusterMempool.getClusterCount()} clusters)` + ); + + if (newBlock) { + await this.runComparison(); + } else if (Date.now() >= this.nextComparisonTime) { + await this.runComparison(); + this.nextComparisonTime = Date.now() + this.comparisonInterval; + } + } catch (err: any) { + console.error(`Poll error: ${err.message}`); + } finally { + this.polling = false; + } + } + + private async runComparison(): Promise { + if (!this.clusterMempool) { + return; + } + this.stats.comparisons++; + console.log(`\n=== Comparison #${this.stats.comparisons} ===`); + + // Step 1: Get Core's block template + let template: any; + try { + template = await this.rpc.getBlockTemplate(['segwit']); + } catch (err: any) { + console.log(`getblocktemplate failed: ${err.message}`); + return; + } + const coreTxids: string[] = template.transactions.map((t: any) => t.txid); + const coreSet = new Set(coreTxids); + + const missingTxids: string[] = []; + for (const txid of coreSet) { + if (!this.mempool[txid]) { + missingTxids.push(txid); + } + } + if (missingTxids.length) { + const added = await this.fetchTransactions(missingTxids); + if (added.length) { + console.log(`Reconciled: +${added.length} missing txs`); + this.clusterMempool.applyMempoolChange({ added, removed: [], accelerations: {} }); + } + } + + const ourBlocks = this.clusterMempool.getBlocks(1, true); + const ourTxids: string[] = ourBlocks[0]?.txids || []; + + await this.compareTemplateOrdering(coreTxids, ourTxids); + + this.printStats(); + + if (this.maxComparisons > 0 && this.stats.comparisons >= this.maxComparisons) { + console.log(`\nReached ${this.maxComparisons} comparisons, stopping.`); + this.shutdown(); + } + } + + private async compareTemplateOrdering(coreTxids: string[], ourTxids: string[]): Promise { + const coreSet = new Set(coreTxids); + const ourSet = new Set(ourTxids); + + let inBoth = 0; + let onlyCore = 0; + let onlyOurs = 0; + for (const txid of coreSet) { + if (ourSet.has(txid)) { + inBoth++; + } else { + onlyCore++; + } + } + for (const txid of ourSet) { + if (!coreSet.has(txid)) { + onlyOurs++; + } + } + + console.log(`Template sets: Core ${coreTxids.length} txs, Ours ${ourTxids.length} txs`); + console.log(` In both: ${inBoth}, Only Core: ${onlyCore}, Only ours: ${onlyOurs}`); + + // Build position maps for the intersection + const corePos = new Map(); + for (let i = 0; i < coreTxids.length; i++) { + if (ourSet.has(coreTxids[i])) { + corePos.set(coreTxids[i], i); + } + } + + // Filter to shared txids in each order + const sharedInCoreOrder = coreTxids.filter(t => ourSet.has(t)); + const sharedInOurOrder = ourTxids.filter(t => coreSet.has(t)); + + let exactMatches = 0; + let firstDivergence = -1; + for (let i = 0; i < sharedInCoreOrder.length; i++) { + if (sharedInCoreOrder[i] === sharedInOurOrder[i]) { + exactMatches++; + } else if (firstDivergence === -1) { + firstDivergence = i; + } + } + + const orderMatchRate = sharedInCoreOrder.length > 0 + ? (exactMatches / sharedInCoreOrder.length * 100).toFixed(1) + : 'N/A'; + console.log(` Ordering: ${orderMatchRate}% exact position match (${exactMatches}/${sharedInCoreOrder.length} shared txs)`); + + if (firstDivergence >= 0) { + const coreTx = sharedInCoreOrder[firstDivergence]; + const ourTx = sharedInOurOrder[firstDivergence]; + console.log(`\n First divergence at position ${firstDivergence}:`); + console.log(` Core wants: ${coreTx}`); + console.log(` We placed: ${ourTx} (Core has this at position ${corePos.get(ourTx)})`); + + for (const [label, txid] of [['Core tx', coreTx], ['Our tx', ourTx]] as const) { + const inOurMempool = !!this.mempool[txid]; + const info = this.clusterMempool?.getClusterInfo(txid); + console.log(`\n --- ${label}: ${txid} ---`); + console.log(` In our mempool: ${inOurMempool}`); + if (inOurMempool) { + const tx = this.mempool[txid]; + console.log(` fee=${tx.fee} weight=${tx.weight} vsize=${tx.vsize} sigops=${tx.sigops} adjustedVsize=${tx.adjustedVsize}`); + } + if (info) { + console.log(` clusterId=${info.clusterId} chunkIndex=${info.chunkIndex} chunkFeerate=${info.chunkFeerate.toFixed(6)}`); + const cluster = this.clusterMempool?.getCluster(info.clusterId); + if (cluster) { + console.log(` Cluster has ${cluster.chunks.length} chunk(s):`); + for (let ci = 0; ci < cluster.chunks.length; ci++) { + const c = cluster.chunks[ci]; + console.log(` chunk[${ci}]: ${c.txs.length} txs, feerate=${c.feerate.toFixed(6)}`); + if (ci === info.chunkIndex) { + const chunkTxids = c.txs.map((idx: number) => cluster.txs[idx]?.txid).filter(Boolean); + for (const tid of chunkTxids) { + const t = this.mempool[tid]; + const parents = t?.vin + ?.filter(v => !v.is_coinbase && this.mempool[v.txid] && chunkTxids.includes(v.txid)) + .map(v => v.txid.substring(0, 12)) || []; + console.log(` ${tid.substring(0, 12)} fee=${t?.fee} size=${(t?.weight || 0) / 4} sigops=${t?.sigops} parents=[${parents.join(', ')}]`); + } + } + } + } + } else { + console.log(` NOT in our cluster mempool`); + } + } + + for (const [label, txid] of [['Core', coreTx], ['Our', ourTx]] as const) { + try { + const coreCluster = await this.rpc.getMempoolCluster(txid); + console.log(`\n --- Core cluster for ${label} tx ${txid.substring(0, 12)} ---`); + console.log(` Raw response: ${JSON.stringify(coreCluster).substring(0, 2000)}`); + } catch (e: any) { + console.log(` Failed to get Core cluster for ${label} tx: ${e.message}`); + } + } + + this.stats.templateMismatches++; + } else if (sharedInCoreOrder.length === coreTxids.length && sharedInCoreOrder.length === ourTxids.length) { + console.log(` PERFECT MATCH`); + this.stats.templateMatches++; + } else { + console.log(` Ordering matches for shared txs, but sets differ`); + this.stats.templateMatches++; + } + } + + private printStats(): void { + console.log('\n--- Cumulative Stats ---'); + const total = this.stats.templateMatches + this.stats.templateMismatches; + console.log(`Template comparisons: ${total} (${this.stats.templateMatches} perfect, ${this.stats.templateMismatches} divergent)`); + } + + private shutdown(): void { + console.log('\nShutting down...'); + if (this.pollTimer) { + clearInterval(this.pollTimer); + } + this.printStats(); + process.exit(0); + } +} + +// ─── Entry Point ──────────────────────────────────────────────────────────── + +const rpcConfig: RpcConfig = { + host: config.CORE_RPC.HOST, + port: config.CORE_RPC.PORT, + user: config.CORE_RPC.USERNAME, + pass: config.CORE_RPC.PASSWORD, +}; + +console.log(`Connecting to Bitcoin Core at ${rpcConfig.host}:${rpcConfig.port}`); + +const harness = new Harness(rpcConfig, cliOptions); +harness.run().catch((err) => { + console.error('Harness failed:', err); + process.exit(1); +}); diff --git a/backend/src/__fixtures__/mempool-config.template.json b/backend/src/__fixtures__/mempool-config.template.json index 0ca5654a5..688a9ba37 100644 --- a/backend/src/__fixtures__/mempool-config.template.json +++ b/backend/src/__fixtures__/mempool-config.template.json @@ -32,6 +32,8 @@ "AUDIT": true, "RUST_GBT": false, "LIMIT_GBT": false, + "CLUSTER_MEMPOOL": false, + "CLUSTER_MEMPOOL_INDEXING": false, "CPFP_INDEXING": true, "MAX_BLOCKS_BULK_QUERY": 999, "DISK_CACHE_BLOCK_INTERVAL": 999, diff --git a/backend/src/__integration_tests__/blocks-repository.test.ts b/backend/src/__integration_tests__/blocks-repository.test.ts index b9f449538..91d0353a0 100644 --- a/backend/src/__integration_tests__/blocks-repository.test.ts +++ b/backend/src/__integration_tests__/blocks-repository.test.ts @@ -40,7 +40,7 @@ describe('BlocksRepository Integration Tests', () => { }); const block = await BlocksRepository.$getBlockByHeight(height); - + expect(block).toBeDefined(); expect(block!.height).toBe(height); expect(block!.id).toBe(blockHash); @@ -58,7 +58,7 @@ describe('BlocksRepository Integration Tests', () => { }); const block = await BlocksRepository.$getBlockByHash(blockHash); - + expect(block).toBeDefined(); expect(block!.id).toBe(blockHash); expect(block!.height).toBe(height); @@ -71,36 +71,36 @@ describe('BlocksRepository Integration Tests', () => { test('should check for missing blocks in range', async () => { // Insert blocks with a gap - await insertTestBlock({ - height: 800100, + await insertTestBlock({ + height: 800100, hash: '0000000000000000000100000000000000000000000000000000000000000001', poolId: defaultPoolId }); - await insertTestBlock({ - height: 800102, + await insertTestBlock({ + height: 800102, hash: '0000000000000000000100000000000000000000000000000000000000000003', poolId: defaultPoolId }); const missingBlocks = await BlocksRepository.$getMissingBlocksBetweenHeights(800100, 800102); - + expect(missingBlocks).toContain(800101); }); test('should get latest block height', async () => { - await insertTestBlock({ - height: 800200, + await insertTestBlock({ + height: 800200, hash: '0000000000000000000200000000000000000000000000000000000000000001', poolId: defaultPoolId }); - await insertTestBlock({ - height: 800201, + await insertTestBlock({ + height: 800201, hash: '0000000000000000000200000000000000000000000000000000000000000002', poolId: defaultPoolId }); const height = await BlocksRepository.$mostRecentBlockHeight(); - + expect(height).toBe(800201); }); @@ -121,7 +121,7 @@ describe('BlocksRepository Integration Tests', () => { }); const block = await BlocksRepository.$getBlockByHash(blockHash); - + expect(block).toBeDefined(); expect(block).not.toBeNull(); // The pool should be populated with the test pool's data diff --git a/backend/src/__integration_tests__/database-migration.test.ts b/backend/src/__integration_tests__/database-migration.test.ts index 7b8a5dd74..9270f9fc8 100644 --- a/backend/src/__integration_tests__/database-migration.test.ts +++ b/backend/src/__integration_tests__/database-migration.test.ts @@ -18,7 +18,7 @@ describe('Database Migration Integration Tests', () => { }); test('should have schema version in state table', async () => { - const [result] = await DB.query("SELECT number FROM state WHERE name = 'schema_version'"); + const [result] = await DB.query('SELECT number FROM state WHERE name = \'schema_version\''); expect(result).toHaveLength(1); expect(result[0].number).toBeGreaterThan(0); }); @@ -70,7 +70,7 @@ describe('Database Migration Integration Tests', () => { WHERE TABLE_SCHEMA = 'mempool_test' AND TABLE_NAME = 'blocks'` ); - + const columnNames = columns.map((col: any) => col.COLUMN_NAME); expect(columnNames).toContain('height'); expect(columnNames).toContain('hash'); @@ -87,7 +87,7 @@ describe('Database Migration Integration Tests', () => { WHERE TABLE_SCHEMA = 'mempool_test' AND TABLE_NAME = 'pools'` ); - + const columnNames = columns.map((col: any) => col.COLUMN_NAME); expect(columnNames).toContain('id'); expect(columnNames).toContain('name'); diff --git a/backend/src/__integration_tests__/pools-repository.test.ts b/backend/src/__integration_tests__/pools-repository.test.ts index 7920e7f88..55e503b26 100644 --- a/backend/src/__integration_tests__/pools-repository.test.ts +++ b/backend/src/__integration_tests__/pools-repository.test.ts @@ -43,7 +43,7 @@ describe('PoolsRepository Integration Tests', () => { }); const pool = await PoolsRepository.$getPool('antpool'); - + expect(pool).toBeDefined(); expect(pool!.name).toBe('AntPool'); expect(pool!.slug).toBe('antpool'); @@ -64,7 +64,7 @@ describe('PoolsRepository Integration Tests', () => { }); const pools = await PoolsRepository.$getPools(); - + expect(pools.length).toBeGreaterThanOrEqual(3); }); @@ -77,7 +77,7 @@ describe('PoolsRepository Integration Tests', () => { }); const pool = await PoolsRepository.$getPool('multi-address-pool', false); - + expect(pool).toBeDefined(); const poolAddresses = JSON.parse(pool!.addresses); expect(poolAddresses).toHaveLength(3); @@ -93,7 +93,7 @@ describe('PoolsRepository Integration Tests', () => { }); const pool = await PoolsRepository.$getPool('regex-pool', false); - + expect(pool).toBeDefined(); const poolRegexes = JSON.parse(pool!.regexes); expect(poolRegexes).toHaveLength(2); diff --git a/backend/src/__integration_tests__/test-helpers.ts b/backend/src/__integration_tests__/test-helpers.ts index 74752d9d2..91e47fe2e 100644 --- a/backend/src/__integration_tests__/test-helpers.ts +++ b/backend/src/__integration_tests__/test-helpers.ts @@ -45,7 +45,7 @@ export async function cleanupTestData(): Promise { try { // Disable foreign key checks temporarily for faster cleanup await DB.query('SET FOREIGN_KEY_CHECKS = 0'); - + for (const table of tables) { try { // Use 'silent' error logging to avoid noise for optional tables that don't exist @@ -55,7 +55,7 @@ export async function cleanupTestData(): Promise { // Silently ignore - no need to log since these are expected for optional features } } - + // Re-enable foreign key checks await DB.query('SET FOREIGN_KEY_CHECKS = 1'); } catch (error) { @@ -143,7 +143,7 @@ export async function insertTestBlock(blockData: { const size = blockData.size || 1000000; const weight = blockData.weight || 4000000; const txCount = blockData.tx_count || 2000; - + await DB.query( `INSERT INTO blocks ( height, hash, blockTimestamp, size, weight, tx_count, diff --git a/backend/src/__tests__/api/common.ts b/backend/src/__tests__/api/common.ts index 14ae3c78b..fb245e15a 100644 --- a/backend/src/__tests__/api/common.ts +++ b/backend/src/__tests__/api/common.ts @@ -30,11 +30,34 @@ describe('Common', () => { expect(Common.isNonStandard(tx)).toEqual(true); }); }); - + test('should not misclassify as nonstandard transactions', () => { randomTransactions.forEach((tx) => { expect(Common.isNonStandard(tx)).toEqual(false); }); }); }); + + describe('Effective Fee Statistics', () => { + test('returns safe defaults for blocks with only coinbase', () => { + const coinbaseTx = { weight: 1000, fee: 0, txid: 'coinbase0' }; + const result = Common.calcEffectiveFeeStatistics([coinbaseTx]); + + expect(result.medianFee).toBe(0); + expect(result.feeRange).toEqual([0, 0, 0, 0, 0, 0, 0]); + }); + + test('excludes coinbase from fee stats when multiple txs', () => { + const coinbaseTx = { weight: 1000, fee: 0, txid: 'coinbase0' }; + const tx1 = { weight: 400, fee: 100, txid: 'tx1' }; // vsize 100, rate 1 sat/vB + const tx2 = { weight: 400, fee: 250, txid: 'tx2' }; // vsize 100, rate 2.5 sat/vB + + const result = Common.calcEffectiveFeeStatistics([coinbaseTx, tx1, tx2]); + + // Verify that coinbase (fee 0) was excluded from stats + // Fee range min/max should be > 0 (not affected by coinbase's 0 fee) + expect(result.feeRange[0]).toBeGreaterThan(0); // min fee + expect(result.feeRange[6]).toBeGreaterThan(0); // max fee + }); + }); }); diff --git a/backend/src/__tests__/cluster-mempool/cluster-mempool.test.ts b/backend/src/__tests__/cluster-mempool/cluster-mempool.test.ts new file mode 100644 index 000000000..0e857bd33 --- /dev/null +++ b/backend/src/__tests__/cluster-mempool/cluster-mempool.test.ts @@ -0,0 +1,544 @@ +import { ClusterMempool } from '../../cluster-mempool/cluster-mempool'; +import { makeTx, txid } from './test-utils'; +import { MempoolTransactionExtended } from '../../mempool.interfaces'; + +function buildMempool(txs: MempoolTransactionExtended[]): { [txid: string]: MempoolTransactionExtended } { + const mempool: { [txid: string]: MempoolTransactionExtended } = {}; + for (const tx of txs) { + mempool[tx.txid] = tx; + } + return mempool; +} + +describe('ClusterMempool', () => { + describe('constructor', () => { + it('should build clusters from mempool', () => { + const parentId = txid('a1'); + const childId = txid('a2'); + const mempool = buildMempool([ + makeTx(parentId, 100, 100), + makeTx(childId, 5000, 100, [parentId]), + ]); + const cm = new ClusterMempool(mempool); + expect(cm.getClusterCount()).toBe(1); + expect(cm.getTxCount()).toBe(2); + }); + + it('should create separate clusters for unrelated txs', () => { + const mempool = buildMempool([ + makeTx(txid('a1'), 100, 100), + makeTx(txid('b1'), 200, 100), + ]); + const cm = new ClusterMempool(mempool); + expect(cm.getClusterCount()).toBe(2); + }); + }); + + describe('getClusterInfo', () => { + it('should return cluster info for a known tx', () => { + const parentId = txid('a1'); + const childId = txid('a2'); + const mempool = buildMempool([ + makeTx(parentId, 100, 100), + makeTx(childId, 5000, 100, [parentId]), + ]); + const cm = new ClusterMempool(mempool); + const info = cm.getClusterInfo(parentId); + expect(info).not.toBeNull(); + expect(info?.chunkFeerate).toBeGreaterThan(0); + }); + + it('should return null for unknown tx', () => { + const mempool = buildMempool([makeTx(txid('a1'), 100, 100)]); + const cm = new ClusterMempool(mempool); + expect(cm.getClusterInfo(txid('zz'))).toBeNull(); + }); + }); + + describe('getCluster', () => { + it('should return cluster data with correct topology', () => { + const parentId = txid('a1'); + const childId = txid('a2'); + const mempool = buildMempool([ + makeTx(parentId, 100, 100), + makeTx(childId, 5000, 100, [parentId]), + ]); + const cm = new ClusterMempool(mempool); + const info = cm.getClusterInfo(parentId); + expect(info).not.toBeNull(); + const data = cm.getCluster(info!.clusterId); + expect(data).not.toBeNull(); + expect(data!.txs.length).toBe(2); + expect(data!.chunks.length).toBeGreaterThan(0); + }); + + it('should return null for unknown cluster id', () => { + const mempool = buildMempool([makeTx(txid('a1'), 100, 100)]); + const cm = new ClusterMempool(mempool); + expect(cm.getCluster(9999)).toBeNull(); + }); + }); + + describe('applyMempoolChange', () => { + it('should handle adding a new singleton tx', () => { + const mempool = buildMempool([makeTx(txid('a1'), 100, 100)]); + const cm = new ClusterMempool(mempool); + const initialCount = cm.getClusterCount(); + + cm.applyMempoolChange({ + added: [makeTx(txid('b1'), 200, 100)], + removed: [], + accelerations: {}, + }); + + expect(cm.getClusterCount()).toBe(initialCount + 1); + expect(cm.getTxCount()).toBe(2); + }); + + it('should skip duplicate tx additions', () => { + const mempool = buildMempool([]); + const cm = new ClusterMempool(mempool); + const tx = makeTx(txid('a1'), 100, 100); + mempool[tx.txid] = tx; + + cm.applyMempoolChange({ + added: [tx, tx], + removed: [], + accelerations: {}, + }); + + expect(cm.getClusterCount()).toBe(1); + expect(cm.getTxCount()).toBe(1); + expect(cm.getBlocks(1)[0].txids).toEqual([tx.txid]); + }); + + it('should handle removing a tx', () => { + const parentId = txid('a1'); + const childId = txid('a2'); + const childTx = makeTx(childId, 5000, 100, [parentId]); + const mempool = buildMempool([ + makeTx(parentId, 100, 100), + childTx, + ]); + const cm = new ClusterMempool(mempool); + + cm.applyMempoolChange({ + added: [], + removed: [childTx], + accelerations: {}, + }); + + expect(cm.getTxCount()).toBe(1); + expect(cm.getClusterInfo(childId)).toBeNull(); + expect(cm.getClusterInfo(parentId)).not.toBeNull(); + }); + + it('should clean spentBy when removed tx is already missing from mempool', () => { + const parentId = txid('a1'); + const childId = txid('a2'); + const childTx = makeTx(childId, 5000, 100, [parentId]); + const mempool = buildMempool([ + makeTx(parentId, 100, 100), + childTx, + ]); + const cm = new ClusterMempool(mempool); + const spentBy = (cm as unknown as { spentBy: Map }).spentBy; + + expect(spentBy.get(`${parentId}:0`)).toBe(childId); + + delete mempool[childId]; + cm.applyMempoolChange({ + added: [], + removed: [childTx], + accelerations: {}, + }); + + expect(spentBy.has(`${parentId}:0`)).toBe(false); + }); + + it('should not delete spentBy for a replacement transaction', () => { + const parentId = txid('a1'); + const replacedId = txid('a2'); + const replacementId = txid('a3'); + const replacedTx = makeTx(replacedId, 5000, 100, [parentId]); + const replacementTx = makeTx(replacementId, 6000, 100, [parentId]); + const mempool = buildMempool([ + makeTx(parentId, 100, 100), + replacementTx, + ]); + const cm = new ClusterMempool(mempool); + const spentBy = (cm as unknown as { spentBy: Map }).spentBy; + + expect(spentBy.get(`${parentId}:0`)).toBe(replacementId); + + cm.applyMempoolChange({ + added: [], + removed: [replacedTx], + accelerations: {}, + }); + + expect(spentBy.get(`${parentId}:0`)).toBe(replacementId); + }); + + it('should split cluster when middle tx is removed', () => { + const a = txid('a1'); + const b = txid('b1'); + const c = txid('c1'); + const bTx = makeTx(b, 200, 100, [a]); + const mempool = buildMempool([ + makeTx(a, 100, 100), + bTx, + makeTx(c, 300, 100, [b]), + ]); + const cm = new ClusterMempool(mempool); + expect(cm.getClusterCount()).toBe(1); + + cm.applyMempoolChange({ + added: [], + removed: [bTx], + accelerations: {}, + }); + + expect(cm.getTxCount()).toBe(2); + const infoA = cm.getClusterInfo(a); + const infoC = cm.getClusterInfo(c); + expect(infoA).not.toBeNull(); + expect(infoC).not.toBeNull(); + expect(infoA!.clusterId).not.toBe(infoC!.clusterId); + }); + + it('should handle fee changes via acceleration', () => { + const parentId = txid('a1'); + const childId = txid('a2'); + const mempool = buildMempool([ + makeTx(parentId, 100, 100), + makeTx(childId, 100, 100, [parentId]), + ]); + const cm = new ClusterMempool(mempool); + const infoBefore = cm.getClusterInfo(childId); + + cm.applyMempoolChange({ + added: [], + removed: [], + accelerations: { [childId]: { feeDelta: 49900 } }, + }); + + const infoAfter = cm.getClusterInfo(childId); + expect(infoAfter).not.toBeNull(); + expect(infoAfter!.clusterId).not.toBe(infoBefore!.clusterId); + }); + + it('should merge clusters when new tx connects them', () => { + const a = txid('a1'); + const b = txid('b1'); + const mempool = buildMempool([ + makeTx(a, 100, 100), + makeTx(b, 200, 100), + ]); + const cm = new ClusterMempool(mempool); + expect(cm.getClusterCount()).toBe(2); + + const bridgeTx = makeTx(txid('c1'), 300, 100, [a, b]); + cm.applyMempoolChange({ + added: [bridgeTx], + removed: [], + accelerations: {}, + }); + + expect(cm.getClusterCount()).toBe(1); + expect(cm.getTxCount()).toBe(3); + }); + }); + + describe('cluster merging', () => { + it('should merge 3 separate clusters when new tx bridges them', () => { + const a = txid('a1'); + const b = txid('b1'); + const c = txid('c1'); + const mempool = buildMempool([ + makeTx(a, 100, 100), + makeTx(b, 200, 100), + makeTx(c, 300, 100), + ]); + const cm = new ClusterMempool(mempool); + expect(cm.getClusterCount()).toBe(3); + + const bridge = makeTx(txid('d1'), 400, 100, [a, b, c]); + mempool[bridge.txid] = bridge; + cm.applyMempoolChange({ added: [bridge], removed: [], accelerations: {} }); + + expect(cm.getClusterCount()).toBe(1); + expect(cm.getTxCount()).toBe(4); + }); + + it('should grow cluster by 1 when new tx has parents in same cluster', () => { + const a = txid('a1'); + const b = txid('b1'); + const mempool = buildMempool([ + makeTx(a, 100, 100), + makeTx(b, 200, 100, [a]), + ]); + const cm = new ClusterMempool(mempool); + expect(cm.getClusterCount()).toBe(1); + + const c = makeTx(txid('c1'), 300, 100, [a]); + mempool[c.txid] = c; + cm.applyMempoolChange({ added: [c], removed: [], accelerations: {} }); + + expect(cm.getClusterCount()).toBe(1); + expect(cm.getTxCount()).toBe(3); + }); + + it('should grow chain incrementally', () => { + const a = txid('a1'); + const mempool = buildMempool([makeTx(a, 100, 100)]); + const cm = new ClusterMempool(mempool); + + const b = makeTx(txid('b1'), 200, 100, [a]); + mempool[b.txid] = b; + cm.applyMempoolChange({ added: [b], removed: [], accelerations: {} }); + + const c = makeTx(txid('c1'), 300, 100, [txid('b1')]); + mempool[c.txid] = c; + cm.applyMempoolChange({ added: [c], removed: [], accelerations: {} }); + + expect(cm.getClusterCount()).toBe(1); + expect(cm.getTxCount()).toBe(3); + }); + }); + + describe('cluster splitting', () => { + it('should split star into singletons when center is removed', () => { + const center = txid('center'); + const leaves = Array.from({ length: 5 }, (_, i) => txid(`leaf${i}`)); + const centerTx = makeTx(center, 100, 100); + for (let i = 1; i < 5; i++) { + centerTx.vout.push({ + scriptpubkey: '', scriptpubkey_asm: '', scriptpubkey_type: 'v0_p2wpkh', value: 50000, + }); + } + const leafTxs = leaves.map((l, i) => { + const tx = makeTx(l, 200, 100, [center]); + tx.vin[0].vout = i; + return tx; + }); + const mempool = buildMempool([centerTx, ...leafTxs]); + const cm = new ClusterMempool(mempool); + expect(cm.getClusterCount()).toBe(1); + + cm.applyMempoolChange({ added: [], removed: [centerTx], accelerations: {} }); + + expect(cm.getTxCount()).toBe(5); + expect(cm.getClusterCount()).toBe(5); + }); + + it('should shrink cluster without splitting when leaf is removed', () => { + const a = txid('a1'); + const b = txid('b1'); + const c = txid('c1'); + const aTx = makeTx(a, 100, 100); + aTx.vout.push({ scriptpubkey: '', scriptpubkey_asm: '', scriptpubkey_type: 'v0_p2wpkh', value: 50000 }); + const bTx = makeTx(b, 200, 100, [a]); + bTx.vin[0].vout = 0; + const cTx = makeTx(c, 300, 100, [a]); + cTx.vin[0].vout = 1; + const mempool = buildMempool([aTx, bTx, cTx]); + const cm = new ClusterMempool(mempool); + expect(cm.getClusterCount()).toBe(1); + + cm.applyMempoolChange({ added: [], removed: [cTx], accelerations: {} }); + + expect(cm.getClusterCount()).toBe(1); + expect(cm.getTxCount()).toBe(2); + }); + + it('should produce singleton when tx is removed from 2-tx cluster', () => { + const a = txid('a1'); + const b = txid('b1'); + const bTx = makeTx(b, 200, 100, [a]); + const mempool = buildMempool([ + makeTx(a, 100, 100), + bTx, + ]); + const cm = new ClusterMempool(mempool); + + cm.applyMempoolChange({ added: [], removed: [bTx], accelerations: {} }); + + expect(cm.getClusterCount()).toBe(1); + expect(cm.getTxCount()).toBe(1); + expect(cm.getClusterInfo(a)).not.toBeNull(); + }); + + it('should create 3+ components when removing a tx that bridges multiple subgraphs', () => { + const hub = txid('hub'); + const a = txid('a1'); + const b = txid('b1'); + const c = txid('c1'); + const hubTx = makeTx(hub, 100, 100); + hubTx.vout.push( + { scriptpubkey: '', scriptpubkey_asm: '', scriptpubkey_type: 'v0_p2wpkh', value: 50000 }, + { scriptpubkey: '', scriptpubkey_asm: '', scriptpubkey_type: 'v0_p2wpkh', value: 50000 }, + ); + const txA = makeTx(a, 200, 100, [hub]); + txA.vin[0].vout = 0; + const txB = makeTx(b, 300, 100, [hub]); + txB.vin[0].vout = 1; + const txC = makeTx(c, 400, 100, [hub]); + txC.vin[0].vout = 2; + const mempool = buildMempool([hubTx, txA, txB, txC]); + const cm = new ClusterMempool(mempool); + expect(cm.getClusterCount()).toBe(1); + + cm.applyMempoolChange({ added: [], removed: [hubTx], accelerations: {} }); + + expect(cm.getClusterCount()).toBe(3); + expect(cm.getTxCount()).toBe(3); + }); + }); + + describe('fee changes via accelerations', () => { + it('should increase chunk feerate when acceleration added', () => { + const a = txid('a1'); + const mempool = buildMempool([makeTx(a, 100, 100)]); + const cm = new ClusterMempool(mempool); + const infoBefore = cm.getClusterInfo(a); + expect(infoBefore).not.toBeNull(); + + cm.applyMempoolChange({ + added: [], + removed: [], + accelerations: { [a]: { feeDelta: 9900 } }, + }); + + const infoAfter = cm.getClusterInfo(a); + expect(infoAfter).not.toBeNull(); + expect(infoAfter!.chunkFeerate).toBeGreaterThan(infoBefore!.chunkFeerate); + }); + + it('should decrease chunk feerate when acceleration removed', () => { + const a = txid('a1'); + const mempool = buildMempool([makeTx(a, 100, 100)]); + const cm = new ClusterMempool(mempool, { [a]: { feeDelta: 9900 } }); + const infoBefore = cm.getClusterInfo(a); + expect(infoBefore).not.toBeNull(); + + cm.applyMempoolChange({ + added: [], + removed: [], + accelerations: {}, + }); + + const infoAfter = cm.getClusterInfo(a); + expect(infoAfter).not.toBeNull(); + expect(infoAfter!.chunkFeerate).toBeLessThan(infoBefore!.chunkFeerate); + }); + + it('should reorder chunks when acceleration shifts priorities', () => { + const a = txid('a1'); + const b = txid('b1'); + const mempool = buildMempool([ + makeTx(a, 100, 100), + makeTx(b, 200, 100, [a]), + ]); + const cm = new ClusterMempool(mempool); + const infoBBefore = cm.getClusterInfo(b); + expect(infoBBefore).not.toBeNull(); + + cm.applyMempoolChange({ + added: [], + removed: [], + accelerations: { [b]: { feeDelta: 49800 } }, + }); + + const infoBAfter = cm.getClusterInfo(b); + expect(infoBAfter).not.toBeNull(); + expect(infoBAfter!.chunkFeerate).toBeGreaterThan(infoBBefore!.chunkFeerate); + }); + }); + + describe('getBlocks', () => { + it('should return projected blocks', () => { + const txs: MempoolTransactionExtended[] = []; + for (let i = 0; i < 10; i++) { + txs.push(makeTx(txid(`t${i}`), 1000 * (i + 1), 100)); + } + const mempool = buildMempool(txs); + const cm = new ClusterMempool(mempool); + const blocks = cm.getBlocks(3); + expect(blocks.length).toBeGreaterThan(0); + expect(blocks[0].txids.length).toBeGreaterThan(0); + }); + + it('should return empty array for empty mempool', () => { + const cm = new ClusterMempool({}); + const blocks = cm.getBlocks(3); + expect(blocks.length).toBe(0); + }); + + it('should respect chunk ordering for single-cluster mempool', () => { + const a = txid('a1'); + const b = txid('b1'); + const c = txid('c1'); + const mempool = buildMempool([ + makeTx(a, 3000, 100), + makeTx(b, 200, 100, [a]), + makeTx(c, 100, 100, [b]), + ]); + const cm = new ClusterMempool(mempool); + const blocks = cm.getBlocks(1); + expect(blocks.length).toBe(1); + const txids = blocks[0].txids; + expect(txids.indexOf(a)).toBeLessThan(txids.indexOf(b)); + expect(txids.indexOf(b)).toBeLessThan(txids.indexOf(c)); + }); + + it('should maintain topological validity within blocks', () => { + const a = txid('a1'); + const b = txid('b1'); + const c = txid('c1'); + const d = txid('d1'); + const aTx = makeTx(a, 1000, 100); + aTx.vout.push({ scriptpubkey: '', scriptpubkey_asm: '', scriptpubkey_type: 'v0_p2wpkh', value: 50000 }); + const bTx = makeTx(b, 500, 100, [a]); + bTx.vin[0].vout = 0; + const cTx = makeTx(c, 500, 100, [a]); + cTx.vin[0].vout = 1; + const mempool = buildMempool([aTx, bTx, cTx, makeTx(d, 200, 100, [b, c])]); + const cm = new ClusterMempool(mempool); + const blocks = cm.getBlocks(1); + const txids = blocks[0].txids; + + expect(txids.indexOf(a)).toBeLessThan(txids.indexOf(b)); + expect(txids.indexOf(a)).toBeLessThan(txids.indexOf(c)); + expect(txids.indexOf(b)).toBeLessThan(txids.indexOf(d)); + expect(txids.indexOf(c)).toBeLessThan(txids.indexOf(d)); + }); + }); + + describe('empty and degenerate cases', () => { + it('should handle empty diff with no changes', () => { + const mempool = buildMempool([makeTx(txid('a1'), 100, 100)]); + const cm = new ClusterMempool(mempool); + const countBefore = cm.getClusterCount(); + const txCountBefore = cm.getTxCount(); + + cm.applyMempoolChange({ added: [], removed: [], accelerations: {} }); + + expect(cm.getClusterCount()).toBe(countBefore); + expect(cm.getTxCount()).toBe(txCountBefore); + }); + + it('should not crash when removing nonexistent tx', () => { + const mempool = buildMempool([makeTx(txid('a1'), 100, 100)]); + const cm = new ClusterMempool(mempool); + + cm.applyMempoolChange({ + added: [], + removed: [makeTx(txid('nonexistent'), 100, 100)], + accelerations: {}, + }); + + expect(cm.getTxCount()).toBe(1); + }); + }); +}); diff --git a/backend/src/__tests__/cluster-mempool/depgraph.test.ts b/backend/src/__tests__/cluster-mempool/depgraph.test.ts new file mode 100644 index 000000000..8fd9c48f1 --- /dev/null +++ b/backend/src/__tests__/cluster-mempool/depgraph.test.ts @@ -0,0 +1,494 @@ +import { DepGraph, sortTopological, subgraph } from '../../cluster-mempool/depgraph'; +import { buildChain, buildFanOut, buildDiamond, buildStar } from './test-utils'; + +describe('DepGraph', () => { + describe('addTransaction', () => { + it('should add a transaction and return a ClusterTx', () => { + const dg = new DepGraph(); + const tx = dg.addTransaction('tx0', 1000, 100); + expect(dg.size).toBe(1); + expect(tx.effectiveFee).toBe(1000); + expect(tx.weight).toBe(100); + expect(tx.txid).toBe('tx0'); + }); + + it('should assign distinct ClusterTx objects', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 10); + const b = dg.addTransaction('b', 200, 20); + const c = dg.addTransaction('c', 300, 30); + expect(a).not.toBe(b); + expect(b).not.toBe(c); + expect(dg.size).toBe(3); + }); + + it('should include self in ancestors and descendants', () => { + const dg = new DepGraph(); + const tx = dg.addTransaction('tx0', 100, 10); + expect(tx.ancestors.has(tx)).toBe(true); + expect(tx.descendants.has(tx)).toBe(true); + }); + + it('should handle large clusters', () => { + const dg = new DepGraph(); + for (let i = 0; i < 100; i++) { + dg.addTransaction(`tx${i}`, 100, 10); + } + expect(dg.size).toBe(100); + }); + }); + + describe('addDependency', () => { + it('should establish parent-child relationship', () => { + const dg = new DepGraph(); + const parent = dg.addTransaction('parent', 100, 10); + const child = dg.addTransaction('child', 200, 20); + dg.addDependency(parent, child); + + expect(child.ancestors.has(parent)).toBe(true); + expect(parent.descendants.has(child)).toBe(true); + expect(child.parents.has(parent)).toBe(true); + expect(parent.children.has(child)).toBe(true); + }); + + it('should propagate ancestors transitively', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 10); + const b = dg.addTransaction('b', 200, 20); + const c = dg.addTransaction('c', 300, 30); + dg.addDependency(a, b); + dg.addDependency(b, c); + + expect(c.ancestors.has(a)).toBe(true); + expect(c.ancestors.has(b)).toBe(true); + expect(c.ancestors.has(c)).toBe(true); + + expect(a.descendants.has(a)).toBe(true); + expect(a.descendants.has(b)).toBe(true); + expect(a.descendants.has(c)).toBe(true); + }); + + it('should handle diamond dependencies', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 10); + const b = dg.addTransaction('b', 200, 20); + const c = dg.addTransaction('c', 300, 30); + const d = dg.addTransaction('d', 400, 40); + dg.addDependency(a, b); + dg.addDependency(a, c); + dg.addDependency(b, d); + dg.addDependency(c, d); + + expect(d.ancestors.size).toBe(4); + expect(a.descendants.size).toBe(4); + }); + }); + + describe('removeTransactions', () => { + it('should remove transactions and update sets', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 10); + const b = dg.addTransaction('b', 200, 20); + const c = dg.addTransaction('c', 300, 30); + dg.addDependency(a, b); + dg.addDependency(b, c); + + dg.removeTransactions(new Set([b])); + expect(dg.size).toBe(2); + expect(dg.hasTx(b)).toBe(false); + expect(c.ancestors.has(b)).toBe(false); + expect(a.descendants.has(b)).toBe(false); + }); + + it('should handle slot reuse after removal', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 10); + dg.addTransaction('b', 200, 20); + dg.removeTransactions(new Set([a])); + const c = dg.addTransaction('c', 300, 30); + expect(dg.size).toBe(2); + expect(c.txid).toBe('c'); + }); + }); + + describe('dependsOn (via ancestors)', () => { + it('should correctly identify dependencies', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 10); + const b = dg.addTransaction('b', 200, 20); + const c = dg.addTransaction('c', 300, 30); + dg.addDependency(a, b); + + expect(b.ancestors.has(a)).toBe(true); + expect(a.ancestors.has(b)).toBe(false); + expect(c.ancestors.has(a)).toBe(false); + }); + }); + + describe('findConnectedComponents', () => { + it('should find a single component', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 10); + const b = dg.addTransaction('b', 200, 20); + dg.addDependency(a, b); + + const components = dg.findConnectedComponents(); + expect(components.length).toBe(1); + expect(components[0].size).toBe(2); + }); + + it('should find multiple disconnected components', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 10); + const b = dg.addTransaction('b', 200, 20); + const c = dg.addTransaction('c', 300, 30); + const d = dg.addTransaction('d', 400, 40); + dg.addDependency(a, b); + dg.addDependency(c, d); + + const components = dg.findConnectedComponents(); + expect(components.length).toBe(2); + }); + + it('should handle isolated transactions', () => { + const dg = new DepGraph(); + dg.addTransaction('a', 100, 10); + dg.addTransaction('b', 200, 20); + dg.addTransaction('c', 300, 30); + + const components = dg.findConnectedComponents(); + expect(components.length).toBe(3); + }); + }); + + describe('parents / children (direct)', () => { + it('should return only direct parents, not transitive', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 10); + const b = dg.addTransaction('b', 200, 20); + const c = dg.addTransaction('c', 300, 30); + dg.addDependency(a, b); + dg.addDependency(b, c); + + expect(c.parents.has(b)).toBe(true); + expect(c.parents.has(a)).toBe(false); + }); + + it('should return only direct children', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 10); + const b = dg.addTransaction('b', 200, 20); + const c = dg.addTransaction('c', 300, 30); + dg.addDependency(a, b); + dg.addDependency(b, c); + + expect(a.children.has(b)).toBe(true); + expect(a.children.has(c)).toBe(false); + }); + }); + + describe('appendTopo', () => { + it('should output in topological order', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 10); + const b = dg.addTransaction('b', 200, 20); + const c = dg.addTransaction('c', 300, 30); + dg.addDependency(a, b); + dg.addDependency(b, c); + + const output = sortTopological(new Set([c, a, b])); + expect(output.indexOf(a)).toBeLessThan(output.indexOf(b)); + expect(output.indexOf(b)).toBeLessThan(output.indexOf(c)); + }); + }); + + describe('restrict', () => { + it('should create a subgraph with correct deps', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 10); + const b = dg.addTransaction('b', 200, 20); + dg.addTransaction('c', 300, 30); + dg.addDependency(a, b); + + const { depgraph: sub, txMap } = subgraph(new Set([a, b])); + expect(sub.size).toBe(2); + const newA = txMap.get(a)!; + const newB = txMap.get(b)!; + expect(newB.ancestors.has(newA)).toBe(true); + }); + }); + + describe('graceful error handling', () => { + it('should no-op addDependency with non-member txs', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 10); + const dg2 = new DepGraph(); + const foreign = dg2.addTransaction('foreign', 200, 20); + dg.addDependency(a, foreign); + dg.addDependency(foreign, a); + expect(a.ancestors.size).toBe(1); + }); + }); + + describe('deep chain topology', () => { + it('should track ancestors and descendants at each depth', () => { + const { depgraph, txs } = buildChain(20, 100, 10); + expect(depgraph.size).toBe(20); + + expect(txs[19].ancestors.size).toBe(20); + expect(txs[0].descendants.size).toBe(20); + + expect(txs[10].ancestors.size).toBe(11); + expect(txs[10].descendants.size).toBe(10); + + expect(txs[10].parents.size).toBe(1); + expect(txs[10].parents.has(txs[9])).toBe(true); + expect(txs[10].children.size).toBe(1); + expect(txs[10].children.has(txs[11])).toBe(true); + }); + }); + + describe('wide fan-out topology', () => { + it('should track parent-children relationships for 1 parent with 10 children', () => { + const { depgraph, parent, children } = buildFanOut(10, 100, 10, 50, 10); + expect(depgraph.size).toBe(11); + expect(parent.children.size).toBe(10); + expect(parent.descendants.size).toBe(11); + + for (const child of children) { + expect(child.ancestors.size).toBe(2); + expect(child.ancestors.has(parent)).toBe(true); + expect(child.parents.size).toBe(1); + expect(child.parents.has(parent)).toBe(true); + } + }); + }); + + describe('wide fan-in topology', () => { + it('should track many parents converging to one child', () => { + const dg = new DepGraph(); + const parents: any[] = []; + for (let i = 0; i < 10; i++) { + parents.push(dg.addTransaction(`p${i}`, 100, 10)); + } + const child = dg.addTransaction('child', 500, 50); + for (const p of parents) { + dg.addDependency(p, child); + } + + expect(child.parents.size).toBe(10); + expect(child.ancestors.size).toBe(11); + for (const p of parents) { + expect(p.descendants.has(child)).toBe(true); + } + }); + }); + + describe('multiple diamonds in sequence', () => { + it('should handle A→B,C→D→E,F→G topology', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 10); + const b = dg.addTransaction('b', 100, 10); + const c = dg.addTransaction('c', 100, 10); + const d = dg.addTransaction('d', 100, 10); + const e = dg.addTransaction('e', 100, 10); + const f = dg.addTransaction('f', 100, 10); + const g = dg.addTransaction('g', 100, 10); + dg.addDependency(a, b); + dg.addDependency(a, c); + dg.addDependency(b, d); + dg.addDependency(c, d); + dg.addDependency(d, e); + dg.addDependency(d, f); + dg.addDependency(e, g); + dg.addDependency(f, g); + + expect(g.ancestors.size).toBe(7); + expect(a.descendants.size).toBe(7); + expect(d.parents.size).toBe(2); + expect(d.children.size).toBe(2); + }); + }); + + describe('disconnected subgraphs', () => { + it('should coexist in one DepGraph', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 10); + const b = dg.addTransaction('b', 200, 20); + dg.addDependency(a, b); + + const c = dg.addTransaction('c', 300, 30); + const d = dg.addTransaction('d', 400, 40); + dg.addDependency(c, d); + + const e = dg.addTransaction('e', 500, 50); + + expect(dg.size).toBe(5); + expect(b.ancestors.has(c)).toBe(false); + expect(a.descendants.has(d)).toBe(false); + expect(e.ancestors.size).toBe(1); + expect(dg.findConnectedComponents().length).toBe(3); + }); + }); + + describe('removeTransactions edge cases', () => { + it('should remove a leaf without affecting siblings', () => { + const { depgraph, parent, children } = buildFanOut(3, 100, 10, 50, 10); + depgraph.removeTransactions(new Set([children[2]])); + expect(depgraph.size).toBe(3); + expect(parent.children.size).toBe(2); + expect(depgraph.hasTx(children[0])).toBe(true); + expect(depgraph.hasTx(children[1])).toBe(true); + }); + + it('should remove a root without affecting unrelated txs', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 10); + const b = dg.addTransaction('b', 200, 20); + dg.addDependency(a, b); + dg.removeTransactions(new Set([a])); + expect(dg.size).toBe(1); + expect(dg.hasTx(a)).toBe(false); + expect(b.ancestors.size).toBe(1); + expect(b.ancestors.has(b)).toBe(true); + }); + + it('should break transitive edges when middle of chain is removed', () => { + const { depgraph, txs } = buildChain(5, 100, 10); + depgraph.removeTransactions(new Set([txs[2]])); + expect(depgraph.size).toBe(4); + expect(txs[0].descendants.has(txs[3])).toBe(false); + expect(txs[0].descendants.has(txs[1])).toBe(true); + expect(txs[3].ancestors.has(txs[0])).toBe(false); + expect(txs[3].descendants.has(txs[4])).toBe(true); + }); + + it('should handle batch removal of multiple txs', () => { + const { depgraph, txs } = buildChain(5, 100, 10); + depgraph.removeTransactions(new Set([txs[1], txs[3]])); + expect(depgraph.size).toBe(3); + expect(depgraph.hasTx(txs[0])).toBe(true); + expect(depgraph.hasTx(txs[2])).toBe(true); + expect(depgraph.hasTx(txs[4])).toBe(true); + }); + + it('should result in empty graph when all txs removed', () => { + const { depgraph, txs } = buildChain(3, 100, 10); + depgraph.removeTransactions(new Set(txs)); + expect(depgraph.size).toBe(0); + expect(depgraph.getTxs().size).toBe(0); + }); + + it('should produce clean state when new tx is added after removal', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 10); + const b = dg.addTransaction('b', 200, 20); + dg.addDependency(a, b); + dg.removeTransactions(new Set([a])); + + const c = dg.addTransaction('c', 300, 30); + expect(c.ancestors.size).toBe(1); + expect(c.descendants.size).toBe(1); + expect(c.ancestors.has(c)).toBe(true); + expect(b.ancestors.has(c)).toBe(false); + }); + }); + + describe('findConnectedComponents after removal', () => { + it('should split into 2 components when bridge tx is removed', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 10); + const b = dg.addTransaction('b', 200, 20); + const c = dg.addTransaction('c', 300, 30); + const d = dg.addTransaction('d', 400, 40); + dg.addDependency(a, b); + dg.addDependency(b, c); + dg.addDependency(d, c); + + dg.removeTransactions(new Set([b])); + const components = dg.findConnectedComponents(); + expect(components.length).toBe(2); + const sizes = components.map(comp => comp.size).sort((x, y) => x - y); + expect(sizes).toEqual([1, 2]); + }); + + it('should produce N singletons when center of star is removed', () => { + const { depgraph, center, leaves } = buildStar(5, 100, 10, 50, 10); + depgraph.removeTransactions(new Set([center])); + const components = depgraph.findConnectedComponents(); + expect(components.length).toBe(5); + for (const comp of components) { + expect(comp.size).toBe(1); + } + }); + + it('should remain 1 component when non-bridge tx is removed', () => { + const { depgraph, txs } = buildDiamond([100, 200, 300, 400], [10, 20, 30, 40]); + depgraph.removeTransactions(new Set([txs[1]])); + const components = depgraph.findConnectedComponents(); + expect(components.length).toBe(1); + }); + }); + + describe('restrict edge cases', () => { + it('should restrict to a single tx', () => { + const { depgraph, txs } = buildChain(3, 100, 10); + const { depgraph: sub } = subgraph(new Set([txs[1]])); + expect(sub.size).toBe(1); + }); + + it('should preserve edges within partial chain subset', () => { + const { depgraph, txs } = buildChain(5, 100, 10); + const subset = new Set([txs[0], txs[1], txs[2]]); + const { depgraph: sub, txMap } = subgraph(subset); + expect(sub.size).toBe(3); + + const newA = txMap.get(txs[0]); + const newB = txMap.get(txs[1]); + const newC = txMap.get(txs[2]); + if (!newA || !newB || !newC) { + throw new Error('txMap missing entries'); + } + expect(newB.ancestors.has(newA)).toBe(true); + expect(newC.ancestors.has(newB)).toBe(true); + }); + }); + + describe('sortTopological edge cases', () => { + it('should handle subset with no internal dependencies', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 10); + const b = dg.addTransaction('b', 200, 20); + const c = dg.addTransaction('c', 300, 30); + + const output = sortTopological(new Set([a, b, c])); + expect(output.length).toBe(3); + expect(new Set(output).size).toBe(3); + }); + + it('should handle single-tx subset', () => { + const { txs } = buildChain(3, 100, 10); + const output = sortTopological(new Set([txs[1]])); + expect(output).toEqual([txs[1]]); + }); + }); + + describe('addDependency edge cases', () => { + it('should be idempotent when adding the same dependency twice', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 10); + const b = dg.addTransaction('b', 200, 20); + dg.addDependency(a, b); + dg.addDependency(a, b); + expect(b.ancestors.size).toBe(2); + expect(a.descendants.size).toBe(2); + }); + + it('should handle redundant edge when parent is already transitive ancestor', () => { + const { depgraph, txs } = buildChain(3, 100, 10); + depgraph.addDependency(txs[0], txs[2]); + expect(txs[2].ancestors.size).toBe(3); + expect(txs[2].parents.size).toBe(2); + }); + }); +}); diff --git a/backend/src/__tests__/cluster-mempool/linearize.test.ts b/backend/src/__tests__/cluster-mempool/linearize.test.ts new file mode 100644 index 000000000..1371c59c2 --- /dev/null +++ b/backend/src/__tests__/cluster-mempool/linearize.test.ts @@ -0,0 +1,485 @@ +import { DepGraph } from '../../cluster-mempool/depgraph'; +import { chunkify, postLinearize, spanningForestLinearize, linearizeCluster } from '../../cluster-mempool/linearize'; +import { buildChain, buildFanOut, buildStar, verifyLinearization, verifyTopologicalOrder } from './test-utils'; + +describe('chunkify', () => { + it('should create one chunk per tx when feerates are decreasing', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 300, 10); + const b = dg.addTransaction('b', 200, 10); + const c = dg.addTransaction('c', 100, 10); + + const chunks = chunkify([a, b, c]); + expect(chunks.length).toBe(3); + expect(chunks[0].txs).toEqual([a]); + expect(chunks[1].txs).toEqual([b]); + expect(chunks[2].txs).toEqual([c]); + }); + + it('should merge all into one chunk when feerates are increasing', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 10); + const b = dg.addTransaction('b', 200, 10); + const c = dg.addTransaction('c', 300, 10); + + const chunks = chunkify([a, b, c]); + expect(chunks.length).toBe(1); + expect(chunks[0].txs).toEqual([a, b, c]); + expect(chunks[0].fee).toBe(600); + expect(chunks[0].weight).toBe(30); + }); + + it('should NOT merge equal feerates (matching Core behavior)', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 10); + const b = dg.addTransaction('b', 100, 10); + + const chunks = chunkify([a, b]); + expect(chunks.length).toBe(2); + }); + + it('should handle single transaction', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 500, 50); + + const chunks = chunkify([a]); + expect(chunks.length).toBe(1); + expect(chunks[0].fee).toBe(500); + expect(chunks[0].weight).toBe(50); + }); + + it('should handle empty linearization', () => { + const chunks = chunkify([]); + expect(chunks.length).toBe(0); + }); + + it('should produce decreasing chunk feerates', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 10); + const b = dg.addTransaction('b', 50, 10); + const c = dg.addTransaction('c', 200, 10); + const d = dg.addTransaction('d', 30, 10); + + const chunks = chunkify([a, c, b, d]); + for (let i = 1; i < chunks.length; i++) { + const prevRate = chunks[i - 1].fee / chunks[i - 1].weight; + const curRate = chunks[i].fee / chunks[i].weight; + expect(prevRate).toBeGreaterThanOrEqual(curRate); + } + }); +}); + +describe('postLinearize', () => { + it('should improve a bad linearization', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 100); + const b = dg.addTransaction('b', 500, 100); + + const result = postLinearize([a, b]); + expect(result[0]).toBe(b); + expect(result[1]).toBe(a); + }); + + it('should not violate dependencies', () => { + const dg = new DepGraph(); + const parent = dg.addTransaction('parent', 100, 100); + const child = dg.addTransaction('child', 500, 100); + dg.addDependency(parent, child); + + const result = postLinearize([parent, child]); + expect(result.indexOf(parent)).toBeLessThan(result.indexOf(child)); + }); + + it('should handle already-optimal ordering', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 500, 100); + const b = dg.addTransaction('b', 100, 100); + + const result = postLinearize([a, b]); + expect(result).toEqual([a, b]); + }); +}); + +describe('spanningForestLinearize', () => { + it('should sort independent transactions by feerate', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 100); + const b = dg.addTransaction('b', 500, 100); + const c = dg.addTransaction('c', 300, 100); + + const result = spanningForestLinearize(dg.getTxs(), 75000); + expect(result[0]).toBe(b); + expect(result[1]).toBe(c); + expect(result[2]).toBe(a); + }); + + it('should respect dependencies', () => { + const dg = new DepGraph(); + const parent = dg.addTransaction('parent', 100, 100); + const child = dg.addTransaction('child', 500, 100); + dg.addDependency(parent, child); + + const result = spanningForestLinearize(dg.getTxs(), 75000); + expect(result.indexOf(parent)).toBeLessThan(result.indexOf(child)); + }); + + it('should handle CPFP pattern', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 100); + const b = dg.addTransaction('b', 10000, 100); + dg.addDependency(a, b); + + const { chunks } = linearizeCluster(dg.getTxs(), 75000); + expect(chunks.length).toBe(1); + expect(chunks[0].txs.length).toBe(2); + }); + + it('should separate high and low feerate independent txs', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 1000, 100); + const b = dg.addTransaction('b', 100, 100); + + const { chunks } = linearizeCluster(dg.getTxs(), 75000); + expect(chunks.length).toBe(2); + expect(chunks[0].txs).toContain(a); + expect(chunks[1].txs).toContain(b); + }); + + it('should handle single transaction', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 1000, 100); + + const result = spanningForestLinearize(dg.getTxs(), 75000); + expect(result).toEqual([a]); + }); + + it('should handle empty graph', () => { + const dg = new DepGraph(); + const result = spanningForestLinearize(dg.getTxs(), 75000); + expect(result).toEqual([]); + }); +}); + +describe('minimize', () => { + it('should keep equal-feerate chain as individual chunks', () => { + const dg = new DepGraph(); + const txs: any[] = []; + for (let i = 0; i < 5; i++) { + txs.push(dg.addTransaction(`tx${i}`, 19, 140)); + } + for (let i = 1; i < 5; i++) { + dg.addDependency(txs[i - 1], txs[i]); + } + + const { chunks } = linearizeCluster(dg.getTxs(), 75000); + expect(chunks.length).toBe(5); + for (const chunk of chunks) { + expect(chunk.txs.length).toBe(1); + } + }); + + it('should merge chain where child has strictly higher feerate', () => { + const dg = new DepGraph(); + const parent = dg.addTransaction('parent', 100, 200); + const child = dg.addTransaction('child', 900, 100); + dg.addDependency(parent, child); + + const { chunks } = linearizeCluster(dg.getTxs(), 75000); + expect(chunks.length).toBe(1); + expect(chunks[0].txs.length).toBe(2); + }); + + it('should split parent-child with equal feerate', () => { + const dg = new DepGraph(); + const parent = dg.addTransaction('parent', 1720, 344); + const child = dg.addTransaction('child', 1240, 248); + dg.addDependency(parent, child); + + const { chunks } = linearizeCluster(dg.getTxs(), 75000); + expect(chunks.length).toBe(2); + }); + + it('should split disconnected equal-feerate components', () => { + const dg = new DepGraph(); + dg.addTransaction('a', 100, 100); + dg.addTransaction('b', 100, 100); + + const { chunks } = linearizeCluster(dg.getTxs(), 75000); + expect(chunks.length).toBe(2); + expect(chunks[0].txs.length).toBe(1); + expect(chunks[1].txs.length).toBe(1); + }); +}); + +describe('chunkify edge cases', () => { + it('should produce N separate chunks when all feerates are equal', () => { + const dg = new DepGraph(); + const txs: any[] = []; + for (let i = 0; i < 5; i++) { + txs.push(dg.addTransaction(`tx${i}`, 100, 10)); + } + const chunks = chunkify(txs); + expect(chunks.length).toBe(5); + }); + + it('should handle alternating high/low feerates', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 1000, 10); + const b = dg.addTransaction('b', 100, 10); + const c = dg.addTransaction('c', 1000, 10); + const d = dg.addTransaction('d', 100, 10); + + const chunks = chunkify([a, b, c, d]); + for (let i = 1; i < chunks.length; i++) { + const prevRate = chunks[i - 1].fee * chunks[i].weight; + const curRate = chunks[i].fee * chunks[i - 1].weight; + expect(prevRate).toBeGreaterThanOrEqual(curRate); + } + }); + + it('should merge all when single very high feerate tx is at the end', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 10, 10); + const b = dg.addTransaction('b', 10, 10); + const c = dg.addTransaction('c', 10, 10); + const d = dg.addTransaction('d', 10000, 10); + + const chunks = chunkify([a, b, c, d]); + expect(chunks.length).toBe(1); + expect(chunks[0].txs.length).toBe(4); + }); + + it('should maintain non-increasing feerates for 50+ tx linearization', () => { + const dg = new DepGraph(); + const txs: any[] = []; + for (let i = 0; i < 50; i++) { + txs.push(dg.addTransaction(`tx${i}`, 5000 - i * 100, 100)); + } + const chunks = chunkify(txs); + for (let i = 1; i < chunks.length; i++) { + const prevRate = chunks[i - 1].fee * chunks[i].weight; + const curRate = chunks[i].fee * chunks[i - 1].weight; + expect(prevRate).toBeGreaterThanOrEqual(curRate); + } + }); + + it('should handle zero-fee transaction', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 1000, 100); + const b = dg.addTransaction('b', 0, 100); + + const chunks = chunkify([a, b]); + expect(chunks.length).toBe(2); + expect(chunks[1].fee).toBe(0); + }); +}); + +describe('postLinearize edge cases', () => { + it('should sort three independent txs by feerate', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 100); + const b = dg.addTransaction('b', 500, 100); + const c = dg.addTransaction('c', 300, 100); + + const result = postLinearize([a, c, b]); + expect(result[0]).toBe(b); + expect(result[2]).toBe(a); + }); + + it('should respect parent-child dependency even when child has higher feerate', () => { + const dg = new DepGraph(); + const parent = dg.addTransaction('parent', 100, 100); + const child = dg.addTransaction('child', 1000, 100); + dg.addDependency(parent, child); + + const result = postLinearize([parent, child]); + expect(result[0]).toBe(parent); + expect(result[1]).toBe(child); + }); + + it('should handle chain A→B→C with CPFP-like feerates', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 100); + const b = dg.addTransaction('b', 200, 100); + const c = dg.addTransaction('c', 10000, 100); + dg.addDependency(a, b); + dg.addDependency(b, c); + + const result = postLinearize([a, b, c]); + verifyTopologicalOrder(result); + }); +}); + +describe('SFL adversarial topologies', () => { + it('should handle comb pattern: one root with many children at different feerates', () => { + const dg = new DepGraph(); + const root = dg.addTransaction('root', 100, 100); + for (let i = 0; i < 8; i++) { + const child = dg.addTransaction(`child${i}`, (i + 1) * 500, 100); + dg.addDependency(root, child); + } + + const { linearization, chunks } = linearizeCluster(dg.getTxs(), 75000); + verifyLinearization(dg.getTxs(), linearization, chunks); + }); + + it('should handle inverted tree: many leaves → intermediates → root', () => { + const dg = new DepGraph(); + const root = dg.addTransaction('root', 100, 100); + const mid1 = dg.addTransaction('mid1', 200, 100); + const mid2 = dg.addTransaction('mid2', 300, 100); + dg.addDependency(root, mid1); + dg.addDependency(root, mid2); + + for (let i = 0; i < 4; i++) { + const leaf = dg.addTransaction(`leaf${i}`, 5000, 100); + dg.addDependency(i < 2 ? mid1 : mid2, leaf); + } + + const { linearization, chunks } = linearizeCluster(dg.getTxs(), 75000); + verifyLinearization(dg.getTxs(), linearization, chunks); + }); + + it('should handle two parallel chains with shared root', () => { + const dg = new DepGraph(); + const root = dg.addTransaction('root', 100, 100); + + let prev1: any = root; + for (let i = 0; i < 5; i++) { + const tx = dg.addTransaction(`chain1_${i}`, 200, 100); + dg.addDependency(prev1, tx); + prev1 = tx; + } + + let prev2: any = root; + for (let i = 0; i < 3; i++) { + const tx = dg.addTransaction(`chain2_${i}`, 300, 100); + dg.addDependency(prev2, tx); + prev2 = tx; + } + + const { linearization, chunks } = linearizeCluster(dg.getTxs(), 75000); + verifyLinearization(dg.getTxs(), linearization, chunks); + }); + + it('should handle deep CPFP: low-fee chain with high-fee tip', () => { + const { depgraph, txs } = buildChain(6, 10, 100); + txs[5].effectiveFee = 50000; + + const { linearization, chunks } = linearizeCluster(depgraph.getTxs(), 75000); + verifyLinearization(depgraph.getTxs(), linearization, chunks); + expect(chunks[0].txs.length).toBeGreaterThan(1); + }); + + it('should find better result than ancestor-feerate for overlapping high-feerate subsets', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 100); + const b = dg.addTransaction('b', 100, 100); + const c = dg.addTransaction('c', 10000, 100); + dg.addDependency(a, c); + dg.addDependency(b, c); + + const { linearization, chunks } = linearizeCluster(dg.getTxs(), 75000 ); + verifyLinearization(dg.getTxs(), linearization, chunks); + const firstChunkFee = chunks[0].fee; + const firstChunkSize = chunks[0].weight; + expect(firstChunkFee / firstChunkSize).toBeGreaterThan(100 / 100); + }); +}); + +describe('linearizeCluster', () => { + it('should produce valid topological linearization', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 10); + const b = dg.addTransaction('b', 200, 20); + const c = dg.addTransaction('c', 300, 30); + dg.addDependency(a, b); + dg.addDependency(b, c); + + const { linearization } = linearizeCluster(dg.getTxs(), 75000); + expect(linearization.indexOf(a)).toBeLessThan(linearization.indexOf(b)); + expect(linearization.indexOf(b)).toBeLessThan(linearization.indexOf(c)); + }); + + it('should produce monotonically decreasing chunk feerates', () => { + const dg = new DepGraph(); + for (let i = 0; i < 10; i++) { + dg.addTransaction(`tx${i}`, Math.floor(Math.random() * 10000) + 100, Math.floor(Math.random() * 500) + 50); + } + + const { chunks } = linearizeCluster(dg.getTxs(), 75000); + for (let i = 1; i < chunks.length; i++) { + const prevRate = chunks[i - 1].fee * chunks[i].weight; + const curRate = chunks[i].fee * chunks[i - 1].weight; + expect(prevRate).toBeGreaterThanOrEqual(curRate); + } + }); + + it('should handle complex diamond dependency graph', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 1000, 100); + const b = dg.addTransaction('b', 500, 100); + const c = dg.addTransaction('c', 100, 100); + const d = dg.addTransaction('d', 300, 100); + + dg.addDependency(a, b); + dg.addDependency(a, c); + dg.addDependency(b, d); + dg.addDependency(c, d); + + const { linearization, chunks } = linearizeCluster(dg.getTxs(), 75000); + verifyLinearization(dg.getTxs(), linearization, chunks); + }); + + it('should produce at-least-as-good result when given suboptimal hint', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 500, 100); + const b = dg.addTransaction('b', 100, 100); + const c = dg.addTransaction('c', 1000, 100); + + const suboptimal = [b, a, c]; + const { chunks: hintChunks } = linearizeCluster(dg.getTxs(), 75000, suboptimal); + const { chunks: freshChunks } = linearizeCluster(dg.getTxs(), 75000); + + const hintFirstFeerate = hintChunks[0].fee * freshChunks[0].weight; + const freshFirstFeerate = freshChunks[0].fee * hintChunks[0].weight; + expect(hintFirstFeerate).toBeGreaterThanOrEqual(freshFirstFeerate - 1); + }); + + it('should preserve an already-optimal linearization', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 1000, 100); + const b = dg.addTransaction('b', 500, 100); + const c = dg.addTransaction('c', 100, 100); + + const optimal = [a, b, c]; + const { linearization } = linearizeCluster(dg.getTxs(), 75000, optimal); + expect(linearization).toEqual(optimal); + }); + + it('should produce valid linearizations on repeated calls', () => { + const dg = new DepGraph(); + const a = dg.addTransaction('a', 100, 100); + const b = dg.addTransaction('b', 100, 100); + const c = dg.addTransaction('c', 100, 100); + dg.addDependency(a, c); + dg.addDependency(b, c); + + const result1 = linearizeCluster(dg.getTxs(), 75000); + const result2 = linearizeCluster(dg.getTxs(), 75000); + verifyLinearization(dg.getTxs(), result1.linearization, result1.chunks); + verifyLinearization(dg.getTxs(), result2.linearization, result2.chunks); + }); + + it('should pass invariant checks for fan-out topology', () => { + const { depgraph } = buildFanOut(6, 100, 100, 500, 100); + const { linearization, chunks } = linearizeCluster(depgraph.getTxs(), 75000); + verifyLinearization(depgraph.getTxs(), linearization, chunks); + }); + + it('should pass invariant checks for star topology', () => { + const { depgraph } = buildStar(5, 100, 100, 300, 100); + const { linearization, chunks } = linearizeCluster(depgraph.getTxs(), 75000); + verifyLinearization(depgraph.getTxs(), linearization, chunks); + }); +}); diff --git a/backend/src/__tests__/cluster-mempool/test-utils.ts b/backend/src/__tests__/cluster-mempool/test-utils.ts new file mode 100644 index 000000000..c2cf18ba8 --- /dev/null +++ b/backend/src/__tests__/cluster-mempool/test-utils.ts @@ -0,0 +1,175 @@ +import { MempoolTransactionExtended } from '../../mempool.interfaces'; +import { ClusterTx, DepGraph } from '../../cluster-mempool/depgraph'; +import { LinearizationChunk } from '../../cluster-mempool/linearize'; + +export function makeTx( + txid: string, + fee: number, + vsize: number, + parentTxids: string[] = [], +): MempoolTransactionExtended { + const vin = parentTxids.length > 0 + ? parentTxids.map(ptxid => ({ + txid: ptxid, + vout: 0, + is_coinbase: false, + scriptsig: '', + scriptsig_asm: '', + inner_redeemscript_asm: '', + inner_witnessscript_asm: '', + sequence: 0, + witness: [] as string[], + prevout: null, + })) + : [{ + txid: '0000000000000000000000000000000000000000000000000000000000000000', + vout: 0, + is_coinbase: false, + scriptsig: '', + scriptsig_asm: '', + inner_redeemscript_asm: '', + inner_witnessscript_asm: '', + sequence: 0, + witness: [] as string[], + prevout: null, + }]; + + return { + txid, + version: 2, + locktime: 0, + size: vsize, + weight: vsize * 4, + fee, + vin, + vout: [{ + scriptpubkey: '', + scriptpubkey_asm: '', + scriptpubkey_type: 'v0_p2wpkh', + value: 50000, + }], + status: { confirmed: false }, + vsize, + feePerVsize: fee / vsize, + effectiveFeePerVsize: fee / vsize, + order: 0, + sigops: 0, + adjustedVsize: vsize, + adjustedFeePerVsize: fee / vsize, + } as MempoolTransactionExtended; +} + +export function txid(short: string): string { + return short.padStart(64, '0'); +} + +export function buildChain( + n: number, + baseFee: number, + baseSize: number, +): { depgraph: DepGraph; txs: ClusterTx[] } { + const depgraph = new DepGraph(); + const txs: ClusterTx[] = []; + for (let i = 0; i < n; i++) { + txs.push(depgraph.addTransaction(`chain_${i}`, baseFee, baseSize)); + } + for (let i = 1; i < n; i++) { + depgraph.addDependency(txs[i - 1], txs[i]); + } + return { depgraph, txs }; +} + +export function buildFanOut( + nChildren: number, + parentFee: number, + parentSize: number, + childFee: number, + childSize: number, +): { depgraph: DepGraph; parent: ClusterTx; children: ClusterTx[] } { + const depgraph = new DepGraph(); + const parent = depgraph.addTransaction('fanout_parent', parentFee, parentSize); + const children: ClusterTx[] = []; + for (let i = 0; i < nChildren; i++) { + const child = depgraph.addTransaction(`fanout_child_${i}`, childFee, childSize); + depgraph.addDependency(parent, child); + children.push(child); + } + return { depgraph, parent, children }; +} + +export function buildDiamond( + fees: [number, number, number, number], + sizes: [number, number, number, number], +): { depgraph: DepGraph; txs: [ClusterTx, ClusterTx, ClusterTx, ClusterTx] } { + const depgraph = new DepGraph(); + const a = depgraph.addTransaction('diamond_a', fees[0], sizes[0]); + const b = depgraph.addTransaction('diamond_b', fees[1], sizes[1]); + const c = depgraph.addTransaction('diamond_c', fees[2], sizes[2]); + const d = depgraph.addTransaction('diamond_d', fees[3], sizes[3]); + depgraph.addDependency(a, b); + depgraph.addDependency(a, c); + depgraph.addDependency(b, d); + depgraph.addDependency(c, d); + return { depgraph, txs: [a, b, c, d] }; +} + +export function buildStar( + nLeaves: number, + centerFee: number, + centerSize: number, + leafFee: number, + leafSize: number, +): { depgraph: DepGraph; center: ClusterTx; leaves: ClusterTx[] } { + const depgraph = new DepGraph(); + const center = depgraph.addTransaction('star_center', centerFee, centerSize); + const leaves: ClusterTx[] = []; + for (let i = 0; i < nLeaves; i++) { + const leaf = depgraph.addTransaction(`star_leaf_${i}`, leafFee, leafSize); + depgraph.addDependency(center, leaf); + leaves.push(leaf); + } + return { depgraph, center, leaves }; +} + +export function verifyTopologicalOrder(ordering: ClusterTx[]): void { + const positionMap = new Map(); + for (let i = 0; i < ordering.length; i++) { + positionMap.set(ordering[i], i); + } + for (const tx of ordering) { + for (const parent of tx.parents) { + const parentPos = positionMap.get(parent); + const childPos = positionMap.get(tx); + if (parentPos !== undefined && childPos !== undefined) { + expect(parentPos).toBeLessThan(childPos); + } + } + } +} + +export function verifyLinearization( + txs: Set, + linearization: ClusterTx[], + chunks: LinearizationChunk[], +): void { + expect(linearization.length).toBe(txs.size); + const linSet = new Set(linearization); + expect(linSet.size).toBe(linearization.length); + for (const tx of txs) { + expect(linSet.has(tx)).toBe(true); + } + + verifyTopologicalOrder(linearization); + + for (let i = 1; i < chunks.length; i++) { + const prevRate = chunks[i - 1].fee * chunks[i].weight; + const curRate = chunks[i].fee * chunks[i - 1].weight; + expect(prevRate).toBeGreaterThanOrEqual(curRate); + } + + const chunkTxs = chunks.flatMap(c => c.txs); + expect(chunkTxs.length).toBe(linearization.length); + for (let i = 0; i < chunkTxs.length; i++) { + expect(chunkTxs[i]).toBe(linearization[i]); + } +} diff --git a/backend/src/__tests__/config.test.ts b/backend/src/__tests__/config.test.ts index 23b3dd346..42a0b3fc8 100644 --- a/backend/src/__tests__/config.test.ts +++ b/backend/src/__tests__/config.test.ts @@ -45,6 +45,8 @@ describe('Mempool Backend Config', () => { AUDIT: false, RUST_GBT: true, LIMIT_GBT: false, + CLUSTER_MEMPOOL: false, + CLUSTER_MEMPOOL_INDEXING: false, CPFP_INDEXING: false, MAX_BLOCKS_BULK_QUERY: 0, DISK_CACHE_BLOCK_INTERVAL: 6, @@ -144,7 +146,7 @@ describe('Mempool Backend Config', () => { }); expect(config.MEMPOOL_SERVICES).toStrictEqual({ - API: "", + API: '', ACCELERATIONS: false, }); diff --git a/backend/src/api/about.routes.ts b/backend/src/api/about.routes.ts index 2020d111d..f8bd0f2bc 100644 --- a/backend/src/api/about.routes.ts +++ b/backend/src/api/about.routes.ts @@ -1,7 +1,9 @@ -import { Application } from "express"; -import config from "../config"; -import axios from "axios"; -import logger from "../logger"; +import { Application } from 'express'; +import config from '../config'; +import axios from 'axios'; +import logger from '../logger'; + +const PROXY_PATH_SEGMENT_REGEX = /^(?!\.{1,2}$)[^\p{Cc}/?#\\]{1,256}$/u; class AboutRoutes { public initRoutes(app: Application) { @@ -15,8 +17,13 @@ class AboutRoutes { } }) .get(config.MEMPOOL.API_URL_PREFIX + 'donations/images/:id', async (req, res) => { + if (!PROXY_PATH_SEGMENT_REGEX.test(req.params.id)) { + res.status(400).end(); + return; + } + try { - const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/donations/images/${req.params.id}`, { + const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/donations/images/${encodeURIComponent(req.params.id)}`, { responseType: 'stream', timeout: 10000 }); response.data.pipe(res); @@ -33,8 +40,13 @@ class AboutRoutes { } }) .get(config.MEMPOOL.API_URL_PREFIX + 'contributors/images/:id', async (req, res) => { + if (!PROXY_PATH_SEGMENT_REGEX.test(req.params.id)) { + res.status(400).end(); + return; + } + try { - const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/contributors/images/${req.params.id}`, { + const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/contributors/images/${encodeURIComponent(req.params.id)}`, { responseType: 'stream', timeout: 10000 }); response.data.pipe(res); @@ -51,8 +63,13 @@ class AboutRoutes { } }) .get(config.MEMPOOL.API_URL_PREFIX + 'translators/images/:id', async (req, res) => { + if (!PROXY_PATH_SEGMENT_REGEX.test(req.params.id)) { + res.status(400).end(); + return; + } + try { - const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/translators/images/${req.params.id}`, { + const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/translators/images/${encodeURIComponent(req.params.id)}`, { responseType: 'stream', timeout: 10000 }); response.data.pipe(res); @@ -61,7 +78,7 @@ class AboutRoutes { } }) .get(config.MEMPOOL.API_URL_PREFIX + 'services/sponsors', async (req, res) => { - const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`; + const url = `${config.MEMPOOL_SERVICES.API}/sponsors`; try { const response = await axios.get(url, { responseType: 'stream', timeout: 10000 }); response.data.pipe(res); @@ -71,7 +88,12 @@ class AboutRoutes { } }) .get(config.MEMPOOL.API_URL_PREFIX + 'services/account/images/:username/:md5', async (req, res) => { - const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`; + if (!PROXY_PATH_SEGMENT_REGEX.test(req.params.username) || !PROXY_PATH_SEGMENT_REGEX.test(req.params.md5)) { + res.status(400).end(); + return; + } + + const url = `${config.MEMPOOL_SERVICES.API}/account/images/${encodeURIComponent(req.params.username)}/${encodeURIComponent(req.params.md5)}`; try { const response = await axios.get(url, { responseType: 'stream', timeout: 10000 }); response.data.pipe(res); @@ -84,4 +106,4 @@ class AboutRoutes { } } -export default new AboutRoutes(); \ No newline at end of file +export default new AboutRoutes(); diff --git a/backend/src/api/acceleration/acceleration.routes.ts b/backend/src/api/acceleration/acceleration.routes.ts index dc2ce697b..372d1292c 100644 --- a/backend/src/api/acceleration/acceleration.routes.ts +++ b/backend/src/api/acceleration/acceleration.routes.ts @@ -5,16 +5,18 @@ import logger from '../../logger'; import mempool from '../mempool'; import AccelerationRepository from '../../repositories/AccelerationRepository'; +const TXID_REGEX = /^[a-f0-9]{64}$/i; + class AccelerationRoutes { private tag = 'Accelerator'; public initRoutes(app: Application): void { app .get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations', this.$getAcceleratorAccelerations.bind(this)) - .get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations/:txid', this.$getAcceleratorAcceleration.bind(this)) .get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations/history', this.$getAcceleratorAccelerationsHistory.bind(this)) .get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations/history/aggregated', this.$getAcceleratorAccelerationsHistoryAggregated.bind(this)) .get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations/stats', this.$getAcceleratorAccelerationsStats.bind(this)) + .get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations/:txid', this.$getAcceleratorAcceleration.bind(this)) .post(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/estimate', this.$getAcceleratorEstimate.bind(this)) ; } @@ -24,8 +26,9 @@ class AccelerationRoutes { res.status(200).send(Object.values(accelerations)); } + /** @asyncUnsafe */ private async $getAcceleratorAcceleration(req: Request, res: Response): Promise { - if (req.params.txid) { + if (req.params.txid && TXID_REGEX.test(req.params.txid)) { const acceleration = await AccelerationRepository.$getAccelerationInfoForTxid(req.params.txid); if (acceleration) { res.status(200).send(acceleration); @@ -33,10 +36,11 @@ class AccelerationRoutes { res.status(404).send('Acceleration not found'); } } else { - res.status(400).send('txid is required'); + res.status(400).send('invalid txid'); } } + /** @asyncUnsafe */ private async $getAcceleratorAccelerationsHistory(req: Request, res: Response): Promise { const history = await AccelerationRepository.$getAccelerationInfo(null, req.query.blockHeight ? parseInt(req.query.blockHeight as string, 10) : null); res.status(200).send(history.map(accel => ({ @@ -53,9 +57,9 @@ class AccelerationRoutes { } private async $getAcceleratorAccelerationsHistoryAggregated(req: Request, res: Response): Promise { - const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`; + const url = `${config.MEMPOOL_SERVICES.API}/accelerator/accelerations/history/aggregated`; try { - const response = await axios.get(url, { responseType: 'stream', timeout: 10000 }); + const response = await axios.get(url, { params: req.query, responseType: 'stream', timeout: 10000 }); for (const key in response.headers) { res.setHeader(key, response.headers[key]); } @@ -67,9 +71,9 @@ class AccelerationRoutes { } private async $getAcceleratorAccelerationsStats(req: Request, res: Response): Promise { - const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`; + const url = `${config.MEMPOOL_SERVICES.API}/accelerator/accelerations/stats`; try { - const response = await axios.get(url, { responseType: 'stream', timeout: 10000 }); + const response = await axios.get(url, { params: req.query, responseType: 'stream', timeout: 10000 }); for (const key in response.headers) { res.setHeader(key, response.headers[key]); } @@ -81,7 +85,7 @@ class AccelerationRoutes { } private async $getAcceleratorEstimate(req: Request, res: Response): Promise { - const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`; + const url = `${config.MEMPOOL_SERVICES.API}/accelerator/estimate`; try { const response = await axios.post(url, req.body, { responseType: 'stream', timeout: 10000 }); for (const key in response.headers) { @@ -95,4 +99,4 @@ class AccelerationRoutes { } } -export default new AccelerationRoutes(); \ No newline at end of file +export default new AccelerationRoutes(); diff --git a/backend/src/api/audit.ts b/backend/src/api/audit.ts index 7b90c516e..cc1201919 100644 --- a/backend/src/api/audit.ts +++ b/backend/src/api/audit.ts @@ -6,11 +6,28 @@ import transactionUtils from './transaction-utils'; const PROPAGATION_MARGIN = 180; // in seconds, time since a transaction is first seen after which it is assumed to have propagated to all miners +export interface AuditResult { + unseen: string[]; + censored: string[]; + added: string[]; + prioritized: string[]; + fresh: string[]; + sigop: string[]; + fullrbf: string[]; + accelerated: string[]; + matchRate: number; + similarity: number; +} + class Audit { - auditBlock(height: number, transactions: MempoolTransactionExtended[], projectedBlocks: MempoolBlockWithTransactions[], mempool: { [txId: string]: MempoolTransactionExtended }) - : { unseen: string[], censored: string[], added: string[], prioritized: string[], fresh: string[], sigop: string[], fullrbf: string[], accelerated: string[], score: number, similarity: number } { + auditBlock( + height: number, + transactions: MempoolTransactionExtended[], + projectedBlocks: MempoolBlockWithTransactions[], + mempool: { [txId: string]: MempoolTransactionExtended } + ): AuditResult { if (!projectedBlocks?.[0]?.transactionIds || !mempool) { - return { unseen: [], censored: [], added: [], prioritized: [], fresh: [], sigop: [], fullrbf: [], accelerated: [], score: 1, similarity: 1 }; + return { unseen: [], censored: [], added: [], prioritized: [], fresh: [], sigop: [], fullrbf: [], accelerated: [], matchRate: 100, similarity: 1 }; } const matches: string[] = []; // present in both mined block and template @@ -176,6 +193,8 @@ class Audit { } const similarity = projectedWeight ? matchedWeight / projectedWeight : 1; + const matchRate = Math.round(score * 100 * 100) / 100; + return { unseen, censored: Object.keys(isCensored), @@ -185,7 +204,7 @@ class Audit { sigop: [], fullrbf: rbf, accelerated, - score, + matchRate, similarity, }; } diff --git a/backend/src/api/backend-info.ts b/backend/src/api/backend-info.ts index d4500a837..af22f45b2 100644 --- a/backend/src/api/backend-info.ts +++ b/backend/src/api/backend-info.ts @@ -3,9 +3,12 @@ import path from 'path'; import os from 'os'; import { IBackendInfo } from '../mempool.interfaces'; import config from '../config'; +import bitcoinClient from './bitcoin/bitcoin-client'; +import logger from '../logger'; class BackendInfo { private backendInfo: IBackendInfo; + private timer; constructor() { // This file is created by ./fetch-version.ts during building @@ -26,7 +29,28 @@ class BackendInfo { gitCommit: versionInfo.gitCommit, lightning: config.LIGHTNING.ENABLED, backend: config.MEMPOOL.BACKEND, + coreVersion: '?', + osVersion: `${os.type()} ${os.release()}`, }; + + this.timer = setInterval(async () => { + try { + await this.$updateCoreVersion(); + } catch (e) { + logger.err(`Exception in $updateCoreVersion. Reason: ${(e instanceof Error ? e.message : e)}`); + } + }, 10 * 60 * 1000); // every 10 minutes + void this.$updateCoreVersion(); // starting immediately + } + + /** @asyncSafe */ + private async $updateCoreVersion(): Promise { + try { + const networkInfo = await bitcoinClient.getNetworkInfo(); + this.backendInfo.coreVersion = networkInfo.subversion; + } catch (e) { + logger.err(`Exception in $updateCoreVersion. Reason: ${(e instanceof Error ? e.message : e)}`); + } } public getBackendInfo(): IBackendInfo { diff --git a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts index eaa079eb6..36fb0d7b0 100644 --- a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts +++ b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts @@ -57,4 +57,19 @@ export interface HealthCheckHost { unreachable: boolean; checked: boolean; lastChecked: number; + hashes?: { + frontend?: string; + hybrid?: string; + backend?: string; + electrs?: string; + ssr?: string; + core?: string; + os?: string; + lastUpdated?: number; + }; + liquidAudit?: { + pegRatio: number; + bitcoinLastBlockUpdate: number; + liquidLastBlockUpdate: number; + }; } diff --git a/backend/src/api/bitcoin/bitcoin-api.interface.ts b/backend/src/api/bitcoin/bitcoin-api.interface.ts index 5d8371d27..e79b43cc6 100644 --- a/backend/src/api/bitcoin/bitcoin-api.interface.ts +++ b/backend/src/api/bitcoin/bitcoin-api.interface.ts @@ -53,6 +53,7 @@ export namespace IBitcoinApi { nTx: number; // (numeric) The number of transactions in the block previousblockhash: string; // (string) The hash of the previous block nextblockhash: string; // (string) The hash of the next block + dynamic_parameters?: any; // (object) Elements only: dynamic parameters at this block } export interface Transaction { @@ -90,6 +91,7 @@ export namespace IBitcoinApi { }; sequence: number; // (numeric) The script sequence number txinwitness?: string[]; // (string) hex-encoded witness data + pegin_witness?: string[]; // (string) Elements peg-in witness coinbase?: string; is_pegin?: boolean; // (boolean) Elements peg-in } @@ -165,44 +167,44 @@ export namespace IBitcoinApi { timeout: number; // (numeric) the median time past of a block at which the deployment is considered failed if not yet locked in since: number; // (numeric) height of the first block to which the status applies statistics: { // (object) numeric statistics about BIP9 signalling for a softfork (only for started status) - period: number; // (numeric) the length in blocks of the BIP9 signalling period - threshold: number; // (numeric) the number of blocks with the version bit set required to activate the feature - elapsed: number; // (numeric) the number of blocks elapsed since the beginning of the current period - count: number; // (numeric) the number of blocks with the version bit set in the current period - possible: boolean; // (boolean) returns false if there are not enough blocks left in this period to pass activation threshold + period: number; // (numeric) the length in blocks of the BIP9 signalling period + threshold: number; // (numeric) the number of blocks with the version bit set required to activate the feature + elapsed: number; // (numeric) the number of blocks elapsed since the beginning of the current period + count: number; // (numeric) the number of blocks with the version bit set in the current period + possible: boolean; // (boolean) returns false if there are not enough blocks left in this period to pass activation threshold } } export interface BlockStats { - "avgfee": number; - "avgfeerate": number; - "avgtxsize": number; - "blockhash": string; - "feerate_percentiles": [number, number, number, number, number]; - "height": number; - "ins": number; - "maxfee": number; - "maxfeerate": number; - "maxtxsize": number; - "medianfee": number; - "mediantime": number; - "mediantxsize": number; - "minfee": number; - "minfeerate": number; - "mintxsize": number; - "outs": number; - "subsidy": number; - "swtotal_size": number; - "swtotal_weight": number; - "swtxs": number; - "time": number; - "total_out": number; - "total_size": number; - "total_weight": number; - "totalfee": number; - "txs": number; - "utxo_increase": number; - "utxo_size_inc": number; + 'avgfee': number; + 'avgfeerate': number; + 'avgtxsize': number; + 'blockhash': string; + 'feerate_percentiles': [number, number, number, number, number]; + 'height': number; + 'ins': number; + 'maxfee': number; + 'maxfeerate': number; + 'maxtxsize': number; + 'medianfee': number; + 'mediantime': number; + 'mediantxsize': number; + 'minfee': number; + 'minfeerate': number; + 'mintxsize': number; + 'outs': number; + 'subsidy': number; + 'swtotal_size': number; + 'swtotal_weight': number; + 'swtxs': number; + 'time': number; + 'total_out': number; + 'total_size': number; + 'total_weight': number; + 'totalfee': number; + 'txs': number; + 'utxo_increase': number; + 'utxo_size_inc': number; } } @@ -213,26 +215,26 @@ export interface TestMempoolAcceptResult { vsize?: number, fees?: { base: number, - "effective-feerate": number, - "effective-includes": string[], + 'effective-feerate': number, + 'effective-includes': string[], }, ['reject-reason']?: string, } export interface SubmitPackageResult { package_msg: string; - "tx-results": { [wtxid: string]: TxResult }; - "replaced-transactions"?: string[]; + 'tx-results': { [wtxid: string]: TxResult }; + 'replaced-transactions'?: string[]; } export interface TxResult { txid: string; - "other-wtxid"?: string; + 'other-wtxid'?: string; vsize?: number; fees?: { base: number; - "effective-feerate"?: number; - "effective-includes"?: string[]; + 'effective-feerate'?: number; + 'effective-includes'?: string[]; }; error?: string; } diff --git a/backend/src/api/bitcoin/bitcoin-api.ts b/backend/src/api/bitcoin/bitcoin-api.ts index edd1a2a1e..85d548f8e 100644 --- a/backend/src/api/bitcoin/bitcoin-api.ts +++ b/backend/src/api/bitcoin/bitcoin-api.ts @@ -112,6 +112,7 @@ class BitcoinApi implements AbstractBitcoinApi { .then((rpcBlock: IBitcoinApi.Block) => rpcBlock.tx); } + /** @asyncUnsafe */ async $getTxsForBlock(hash: string, fallbackToCore = false): Promise { const verboseBlock: IBitcoinApi.VerboseBlock = await this.bitcoindClient.getBlock(hash, 2); const transactions: IEsploraApi.Transaction[] = []; @@ -130,7 +131,7 @@ class BitcoinApi implements AbstractBitcoinApi { $getRawBlock(hash: string): Promise { return this.bitcoindClient.getBlock(hash, 0) - .then((raw: string) => Buffer.from(raw, "hex")); + .then((raw: string) => Buffer.from(raw, 'hex')); } $getBlockHash(height: number): Promise { @@ -219,6 +220,7 @@ class BitcoinApi implements AbstractBitcoinApi { return this.bitcoindClient.submitPackage(rawTransactions, maxfeerate ?? undefined, maxburnamount ?? undefined); } + /** @asyncUnsafe */ async $getOutspend(txId: string, vout: number): Promise { const txOut = await this.bitcoindClient.getTxOut(txId, vout, false); return { @@ -229,6 +231,7 @@ class BitcoinApi implements AbstractBitcoinApi { }; } + /** @asyncUnsafe */ async $getOutspends(txId: string): Promise { const outSpends: IEsploraApi.Outspend[] = []; const tx = await this.$getRawTransaction(txId, true, false); @@ -247,6 +250,7 @@ class BitcoinApi implements AbstractBitcoinApi { return outSpends; } + /** @asyncUnsafe */ async $getBatchedOutspends(txId: string[]): Promise { const outspends: IEsploraApi.Outspend[][] = []; for (const tx of txId) { @@ -260,6 +264,7 @@ class BitcoinApi implements AbstractBitcoinApi { return this.$getBatchedOutspends(txId); } + /** @asyncUnsafe */ async $getOutSpendsByOutpoint(outpoints: { txid: string, vout: number }[]): Promise { const outspends: IEsploraApi.Outspend[] = []; for (const outpoint of outpoints) { @@ -269,6 +274,7 @@ class BitcoinApi implements AbstractBitcoinApi { return outspends; } + /** @asyncUnsafe */ async $getCoinbaseTx(blockhash: string): Promise { const txids = await this.$getTxIdsForBlock(blockhash); return this.$getRawTransaction(txids[0]); @@ -283,6 +289,7 @@ class BitcoinApi implements AbstractBitcoinApi { return this.bitcoindClient.getNetworkHashPs(120, blockHeight); } + /** @asyncUnsafe */ protected async $convertTransaction(transaction: IBitcoinApi.Transaction, addPrevout: boolean, lazyPrevouts = false, allowMissingPrevouts = false): Promise { let esploraTransaction: IEsploraApi.Transaction = { txid: transaction.txid, @@ -367,6 +374,7 @@ class BitcoinApi implements AbstractBitcoinApi { } } + /** @asyncUnsafe */ private async $appendMempoolFeeData(transaction: IEsploraApi.Transaction): Promise { if (transaction.fee) { return transaction; @@ -384,6 +392,7 @@ class BitcoinApi implements AbstractBitcoinApi { return transaction; } + /** @asyncUnsafe */ protected async $addPrevouts(transaction: TransactionExtended): Promise { let addedPrevouts = false; for (const vin of transaction.vin) { @@ -423,6 +432,7 @@ class BitcoinApi implements AbstractBitcoinApi { } + /** @asyncUnsafe */ private async $calculateFeeFromInputs(transaction: IEsploraApi.Transaction, addPrevout: boolean, lazyPrevouts: boolean): Promise { if (transaction.vin[0].is_coinbase) { transaction.fee = 0; diff --git a/backend/src/api/bitcoin/bitcoin.routes.ts b/backend/src/api/bitcoin/bitcoin.routes.ts index 69d42f570..2388a5618 100644 --- a/backend/src/api/bitcoin/bitcoin.routes.ts +++ b/backend/src/api/bitcoin/bitcoin.routes.ts @@ -12,7 +12,7 @@ import backendInfo from '../backend-info'; import transactionUtils from '../transaction-utils'; import { IEsploraApi } from './esplora-api.interface'; import loadingIndicators from '../loading-indicators'; -import { TransactionExtended } from '../../mempool.interfaces'; +import { CpfpInfo, TransactionExtended } from '../../mempool.interfaces'; import logger from '../../logger'; import blocks from '../blocks'; import bitcoinClient from './bitcoin-client'; @@ -23,11 +23,14 @@ import { calculateMempoolTxCpfp } from '../cpfp'; import { handleError } from '../../utils/api'; import poolsUpdater from '../../tasks/pools-updater'; import chainTips from '../chain-tips'; +import FlagValueRepository, { INTERVAL_PRESETS } from '../../repositories/FlagValueRepository'; const TXID_REGEX = /^[a-f0-9]{64}$/i; const BLOCK_HASH_REGEX = /^[a-f0-9]{64}$/i; const ADDRESS_REGEX = /^[a-z0-9]{2,120}$/i; const SCRIPT_HASH_REGEX = /^([a-f0-9]{2})+$/i; +const MAX_TRANSACTION_TIMES = 100; +const JUST_NUMBERS_REGEX = /^[1-9]\d*$/; class BitcoinRoutes { public initRoutes(app: Application) { @@ -37,6 +40,7 @@ class BitcoinRoutes { .get(config.MEMPOOL.API_URL_PREFIX + 'difficulty-adjustment', this.getDifficultyChange) .get(config.MEMPOOL.API_URL_PREFIX + 'fees/recommended', this.getRecommendedFees) .get(config.MEMPOOL.API_URL_PREFIX + 'fees/precise', this.getPreciseRecommendedFees) + .get(config.MEMPOOL.API_URL_PREFIX + 'fee-estimates', this.getPreciseRecommendedFeesEsploraTransformed) .get(config.MEMPOOL.API_URL_PREFIX + 'fees/mempool-blocks', this.getMempoolBlocks) .get(config.MEMPOOL.API_URL_PREFIX + 'backend-info', this.getBackendInfo) .get(config.MEMPOOL.API_URL_PREFIX + 'init-data', this.getInitData) @@ -59,6 +63,7 @@ class BitcoinRoutes { .get(config.MEMPOOL.API_URL_PREFIX + 'blocks-bulk/:from/:to', this.getBlocksByBulk.bind(this)) .get(config.MEMPOOL.API_URL_PREFIX + 'chain-tips', this.getChainTips.bind(this)) .get(config.MEMPOOL.API_URL_PREFIX + 'stale-tips', this.getStaleTips.bind(this)) + .get(config.MEMPOOL.API_URL_PREFIX + 'stale-tips/:height', this.getStaleTips.bind(this)) .post(config.MEMPOOL.API_URL_PREFIX + 'prevouts', this.$getPrevouts) .post(config.MEMPOOL.API_URL_PREFIX + 'cpfp', this.getCpfpLocalTxs) // Temporarily add txs/package endpoint for all backends until esplora supports it @@ -67,6 +72,10 @@ class BitcoinRoutes { .get(config.MEMPOOL.API_URL_PREFIX + 'internal/blocks/definition/list', this.getBlockDefinitionHashes) .get(config.MEMPOOL.API_URL_PREFIX + 'internal/blocks/definition/current', this.getCurrentBlockDefinitionHash) .get(config.MEMPOOL.API_URL_PREFIX + 'internal/blocks/:definitionHash', this.getBlocksByDefinitionHash) + + .get(config.MEMPOOL.API_URL_PREFIX + 'goggles/:interval/', this.getTxCountPerFlagValue) + .get(config.MEMPOOL.API_URL_PREFIX + 'goggles/:interval/:bucketSize', this.getTxCountPerFlagValue) + .get(config.MEMPOOL.API_URL_PREFIX + 'goggles/:interval/:bucketSize/:op/:mask', this.getTxCountPerFlagValue) ; if (config.MEMPOOL.BACKEND !== 'esplora') { @@ -129,20 +138,62 @@ class BitcoinRoutes { res.send('Service Unavailable'); return; } - let minFee = 0; - if (req.query.min) { - try { - minFee = parseFloat(req.query.min as string); - } catch (e) { - res.statusCode = 400; - res.send('Invalid minimum fee'); - return; - } - } - const result = feeApi.getPreciseRecommendedFee(minFee); + const result = feeApi.getPreciseRecommendedFee(); res.json(result); } + private getPreciseRecommendedFeesEsploraTransformed(req: Request, res: Response) { + if (!mempool.isInSync()) { + res.statusCode = 503; + res.send('Service Unavailable'); + return; + } + const result = feeApi.getPreciseRecommendedFee(); + + // Note: Manually tested against rust-esplora-client 0.12.3 + // to make sure the status code and extra header didn't blow everything up. + // 3xx redirects with a Location header were silently followed and caused the client to + // blow up, 4xx and 5xx caused errors in the client as well, so only 2xx was acceptable for this endpoint. + + // HTTP 203 Non-Authoritative Information is used to indicate + // that the response is not the original from the server, but a transformed version of it. + res.statusCode = 203; + res.setHeader( + 'x-warning-from-mempool', + `This endpoint is deprecated and will be removed in a future release. Please use /api/v1/fees/precise instead.`, + ); + res.json({ + '1': result.fastestFee, + '2': result.fastestFee, + '3': result.halfHourFee, + '4': result.halfHourFee, + '5': result.halfHourFee, + '6': result.hourFee, + '7': result.hourFee, + '8': result.hourFee, + '9': result.hourFee, + '10': result.hourFee, + '11': result.hourFee, + '12': result.hourFee, + '13': result.hourFee, + '14': result.hourFee, + '15': result.hourFee, + '16': result.hourFee, + '17': result.hourFee, + '18': result.hourFee, + '19': result.hourFee, + '20': result.hourFee, + '21': result.hourFee, + '22': result.hourFee, + '23': result.hourFee, + '24': result.hourFee, + '25': result.hourFee, + '144': result.economyFee, + '504': result.economyFee, + '1008': result.minimumFee, + }); + } + private getMempoolBlocks(req: Request, res: Response) { try { const result = mempoolBlocks.getMempoolBlocks(); @@ -153,17 +204,21 @@ class BitcoinRoutes { } private getTransactionTimes(req: Request, res: Response) { - if (!Array.isArray(req.query.txId)) { - handleError(req, res, 500, 'Not an array'); + if (!req.query.txId || typeof req.query.txId !== 'object') { + handleError(req, res, 400, 'invalid txId format'); return; } + + const requestedTxIds = Object.values(req.query.txId); + if (requestedTxIds.length > MAX_TRANSACTION_TIMES) { + handleError(req, res, 400, 'Too many txids requested'); + return; + } + const txIds: string[] = []; - for (const _txId in req.query.txId) { - if (typeof req.query.txId[_txId] === 'string') { - const txid = req.query.txId[_txId].toString(); - if (TXID_REGEX.test(txid)) { - txIds.push(txid); - } + for (const txid of requestedTxIds) { + if (typeof txid === 'string' && TXID_REGEX.test(txid)) { + txIds.push(txid); } } @@ -174,7 +229,7 @@ class BitcoinRoutes { private async $getBatchedOutspends(req: Request, res: Response): Promise { const txids_csv = req.query.txids; if (!txids_csv || typeof txids_csv !== 'string') { - handleError(req, res, 500, 'Invalid txids format'); + handleError(req, res, 400, 'Invalid txids format'); return; } const txids = txids_csv.split(','); @@ -197,18 +252,18 @@ class BitcoinRoutes { private async $getCpfpInfo(req: Request, res: Response) { if (!TXID_REGEX.test(req.params.txId)) { - handleError(req, res, 501, `Invalid transaction ID`); + handleError(req, res, 400, `Invalid transaction ID`); return; } const tx = mempool.getMempool()[req.params.txId]; if (tx) { if (tx?.cpfpChecked) { - res.json({ - ancestors: tx.ancestors, + const response: CpfpInfo & { acceleratedBy?: number[], acceleratedAt?: number, feeDelta?: number } = { + ancestors: tx.ancestors || [], bestDescendant: tx.bestDescendant || null, - descendants: tx.descendants || null, - effectiveFeePerVsize: tx.effectiveFeePerVsize || null, + descendants: tx.descendants, + effectiveFeePerVsize: tx.effectiveFeePerVsize, sigops: tx.sigops, fee: tx.fee, adjustedVsize: tx.adjustedVsize, @@ -216,7 +271,14 @@ class BitcoinRoutes { acceleratedBy: tx.acceleratedBy || undefined, acceleratedAt: tx.acceleratedAt || undefined, feeDelta: tx.feeDelta || undefined, - }); + }; + if (config.MEMPOOL.CLUSTER_MEMPOOL && tx.clusterId != null) { + const cluster = mempool.clusterMempool?.getClusterForApi(req.params.txId); + if (cluster) { + response.cluster = cluster; + } + } + res.json(response); return; } @@ -252,7 +314,7 @@ class BitcoinRoutes { private async getTransaction(req: Request, res: Response) { if (!TXID_REGEX.test(req.params.txId)) { - handleError(req, res, 501, `Invalid transaction ID`); + handleError(req, res, 400, `Invalid transaction ID`); return; } try { @@ -271,7 +333,7 @@ class BitcoinRoutes { private async getRawTransaction(req: Request, res: Response) { if (!TXID_REGEX.test(req.params.txId)) { - handleError(req, res, 501, `Invalid transaction ID`); + handleError(req, res, 400, `Invalid transaction ID`); return; } try { @@ -359,7 +421,7 @@ class BitcoinRoutes { private async getTransactionStatus(req: Request, res: Response) { if (!TXID_REGEX.test(req.params.txId)) { - handleError(req, res, 501, `Invalid transaction ID`); + handleError(req, res, 400, `Invalid transaction ID`); return; } try { @@ -378,7 +440,7 @@ class BitcoinRoutes { private async getStrippedBlockTransactions(req: Request, res: Response) { if (!BLOCK_HASH_REGEX.test(req.params.hash)) { - handleError(req, res, 501, `Invalid block hash`); + handleError(req, res, 400, `Invalid block hash`); return; } try { @@ -392,11 +454,11 @@ class BitcoinRoutes { private async getStrippedBlockTransaction(req: Request, res: Response) { if (!BLOCK_HASH_REGEX.test(req.params.hash)) { - handleError(req, res, 501, `Invalid block hash`); + handleError(req, res, 400, `Invalid block hash`); return; } if (!TXID_REGEX.test(req.params.txid)) { - handleError(req, res, 501, `Invalid transaction ID`); + handleError(req, res, 400, `Invalid transaction ID`); return; } try { @@ -414,7 +476,7 @@ class BitcoinRoutes { private async getBlock(req: Request, res: Response) { if (!BLOCK_HASH_REGEX.test(req.params.hash)) { - handleError(req, res, 501, `Invalid block hash`); + handleError(req, res, 400, `Invalid block hash`); return; } try { @@ -440,7 +502,7 @@ class BitcoinRoutes { private async getBlockHeader(req: Request, res: Response) { if (!BLOCK_HASH_REGEX.test(req.params.hash)) { - handleError(req, res, 501, `Invalid block hash`); + handleError(req, res, 400, `Invalid block hash`); return; } try { @@ -454,7 +516,7 @@ class BitcoinRoutes { private async getBlockAuditSummary(req: Request, res: Response) { if (!BLOCK_HASH_REGEX.test(req.params.hash)) { - handleError(req, res, 501, `Invalid block hash`); + handleError(req, res, 400, `Invalid block hash`); return; } try { @@ -473,11 +535,11 @@ class BitcoinRoutes { private async $getBlockTxAuditSummary(req: Request, res: Response) { if (!BLOCK_HASH_REGEX.test(req.params.hash)) { - handleError(req, res, 501, `Invalid block hash`); + handleError(req, res, 400, `Invalid block hash`); return; } if (!TXID_REGEX.test(req.params.txid)) { - handleError(req, res, 501, `Invalid transaction ID`); + handleError(req, res, 400, `Invalid transaction ID`); return; } try { @@ -496,7 +558,7 @@ class BitcoinRoutes { private async getBlocks(req: Request, res: Response) { try { - if (['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK)) { // Bitcoin + if (['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) { // Bitcoin const height = req.params.height === undefined ? undefined : parseInt(req.params.height, 10); res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); res.json(await blocks.$getBlocks(height, 15)); @@ -510,7 +572,7 @@ class BitcoinRoutes { private async getBlocksByBulk(req: Request, res: Response) { try { - if (['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK) === false) { // Liquid - Not implemented + if (['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK) === false) { // Liquid - Not implemented handleError(req, res, 404, `This API is only available for Bitcoin networks`); return; } @@ -552,7 +614,7 @@ class BitcoinRoutes { private async getChainTips(req: Request, res: Response) { try { - if (['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK)) { // Bitcoin + if (['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) { // Bitcoin res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); const tips = await chainTips.getChainTips(); if (tips.length > 0) { @@ -572,15 +634,19 @@ class BitcoinRoutes { private async getStaleTips(req: Request, res: Response) { try { - if (['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK)) { // Bitcoin - res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); - const tips = await chainTips.getStaleTips(); - if (tips.length > 0) { - res.json(tips); - } else { - handleError(req, res, 503, `Temporarily unavailable`); - return; + if (['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) { // Bitcoin + let fromHeight: number | undefined; + if (req.params.height !== undefined) { + fromHeight = parseInt(req.params.height, 10); + if (isNaN(fromHeight) || fromHeight < 0) { + handleError(req, res, 400, `Parameter 'height' must be a block height (integer)`); + return; + } } + + res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); + const tips = await chainTips.$getStaleTipsPage(fromHeight, 25); + res.json(tips); } else { // Liquid handleError(req, res, 404, `This API is only available for Bitcoin networks`); return; @@ -627,7 +693,7 @@ class BitcoinRoutes { private async getBlockTransactions(req: Request, res: Response) { if (!BLOCK_HASH_REGEX.test(req.params.hash)) { - handleError(req, res, 501, `Invalid block hash`); + handleError(req, res, 400, `Invalid block hash`); return; } try { @@ -669,7 +735,7 @@ class BitcoinRoutes { return; } if (!ADDRESS_REGEX.test(req.params.address)) { - handleError(req, res, 501, `Invalid address`); + handleError(req, res, 400, `Invalid address`); return; } @@ -677,6 +743,10 @@ class BitcoinRoutes { const addressData = await bitcoinApi.$getAddress(req.params.address); res.json(addressData); } catch (e) { + if (e instanceof Error && e.message === 'Invalid Bitcoin address') { + res.status(400).send(e.message); + return; + } if (e instanceof Error && e.message && (e.message.indexOf('too long') > 0 || e.message.indexOf('confirmed status') > 0)) { handleError(req, res, 413, e.message); return; @@ -691,7 +761,7 @@ class BitcoinRoutes { return; } if (!ADDRESS_REGEX.test(req.params.address)) { - handleError(req, res, 501, `Invalid address`); + handleError(req, res, 400, `Invalid address`); return; } @@ -703,6 +773,10 @@ class BitcoinRoutes { const transactions = await bitcoinApi.$getAddressTransactions(req.params.address, lastTxId); res.json(transactions); } catch (e) { + if (e instanceof Error && e.message === 'Invalid Bitcoin address') { + res.status(400).send(e.message); + return; + } if (e instanceof Error && e.message && (e.message.indexOf('too long') > 0 || e.message.indexOf('confirmed status') > 0)) { handleError(req, res, 413, e.message); return; @@ -717,7 +791,7 @@ class BitcoinRoutes { return; } if (!ADDRESS_REGEX.test(req.params.address)) { - handleError(req, res, 501, `Invalid address`); + handleError(req, res, 400, `Invalid address`); return; } @@ -725,6 +799,10 @@ class BitcoinRoutes { const addressData = await bitcoinApi.$getAddressUtxos(req.params.address); res.json(addressData); } catch (e) { + if (e instanceof Error && e.message === 'Invalid Bitcoin address') { + res.status(400).send(e.message); + return; + } if (e instanceof Error && e.message && (e.message.indexOf('too long') > 0 || e.message.indexOf('confirmed status') > 0)) { handleError(req, res, 413, e.message); return; @@ -746,7 +824,7 @@ class BitcoinRoutes { return; } if (!SCRIPT_HASH_REGEX.test(req.params.scripthash)) { - handleError(req, res, 501, `Invalid scripthash`); + handleError(req, res, 400, `Invalid scripthash`); return; } @@ -770,7 +848,7 @@ class BitcoinRoutes { return; } if (!SCRIPT_HASH_REGEX.test(req.params.scripthash)) { - handleError(req, res, 501, `Invalid scripthash`); + handleError(req, res, 400, `Invalid scripthash`); return; } @@ -798,7 +876,7 @@ class BitcoinRoutes { return; } if (!SCRIPT_HASH_REGEX.test(req.params.scripthash)) { - handleError(req, res, 501, `Invalid scripthash`); + handleError(req, res, 400, `Invalid scripthash`); return; } @@ -931,7 +1009,7 @@ class BitcoinRoutes { private async getRawBlock(req: Request, res: Response) { if (!BLOCK_HASH_REGEX.test(req.params.hash)) { - handleError(req, res, 501, `Invalid block hash`); + handleError(req, res, 400, `Invalid block hash`); return; } try { @@ -945,7 +1023,7 @@ class BitcoinRoutes { private async getTxIdsForBlock(req: Request, res: Response) { if (!BLOCK_HASH_REGEX.test(req.params.hash)) { - handleError(req, res, 501, `Invalid block hash`); + handleError(req, res, 400, `Invalid block hash`); return; } try { @@ -958,7 +1036,7 @@ class BitcoinRoutes { private async validateAddress(req: Request, res: Response) { if (!ADDRESS_REGEX.test(req.params.address)) { - handleError(req, res, 501, `Invalid address`); + handleError(req, res, 400, `Invalid address`); return; } try { @@ -971,7 +1049,7 @@ class BitcoinRoutes { private async getRbfHistory(req: Request, res: Response) { if (!TXID_REGEX.test(req.params.txId)) { - handleError(req, res, 501, `Invalid transaction ID`); + handleError(req, res, 400, `Invalid transaction ID`); return; } try { @@ -1006,7 +1084,7 @@ class BitcoinRoutes { private async getCachedTx(req: Request, res: Response) { if (!TXID_REGEX.test(req.params.txId)) { - handleError(req, res, 501, `Invalid transaction ID`); + handleError(req, res, 400, `Invalid transaction ID`); return; } try { @@ -1023,7 +1101,7 @@ class BitcoinRoutes { private async getTransactionOutspends(req: Request, res: Response) { if (!TXID_REGEX.test(req.params.txId)) { - handleError(req, res, 501, `Invalid transaction ID`); + handleError(req, res, 400, `Invalid transaction ID`); return; } try { @@ -1036,7 +1114,7 @@ class BitcoinRoutes { private async getTransactionMerkleProof(req: Request, res: Response): Promise { if (!TXID_REGEX.test(req.params.txId)) { - handleError(req, res, 501, `Invalid transaction ID`); + handleError(req, res, 400, `Invalid transaction ID`); return; } try { @@ -1060,6 +1138,63 @@ class BitcoinRoutes { } } + private async getTxCountPerFlagValue(req: Request, res: Response) { + try { + if (!Common.blocksSummariesIndexingEnabled()) { + handleError(req, res, 404, `Block summaries indexing is required for this API`); + return; + } + + const presets = INTERVAL_PRESETS; + const operations = ['and', 'or', 'nor', undefined]; + const intervals = Object.keys(presets); + const interval = req.params.interval; + + if (!intervals.includes(interval)) { + handleError(req, res, 400, `Invalid interval, must be one of ${intervals.toString()}`); + return; + } + + const validBucketSizes = presets[interval].bucketSizes; + const rawBucketSize = req.params.bucketSize; + const bucketSize: number = rawBucketSize === undefined ? validBucketSizes[0] : Number(rawBucketSize); + if (!Number.isInteger(bucketSize) || !validBucketSizes.includes(bucketSize)) { + handleError(req, res, 400, `Invalid bucket size, must be ${validBucketSizes.toString()}`); + return; + } + + if (!operations.includes(req.params.op)) { + handleError(req, res, 400, `Invalid operation, must be 'and', 'or', 'nor' or undefined.`); + return; + } + + if (req.params.mask && !JUST_NUMBERS_REGEX.test(req.params.mask)) { + handleError(req, res, 400, `Invalid mask value, must be a positive integer`); + return; + } + + const op = (req.params.op) as 'and' | 'or' | 'nor' | undefined; + const mask = BigInt(req.params.mask ?? 0n); + + const { tip, tail } = await FlagValueRepository.$getTipAndTailIndexedByBucketSize(bucketSize) || { tip: undefined, tail: undefined }; + + if (tip === undefined || tail === undefined) { + handleError(req, res, 400, `Failed to get latest indexed flag values for ${interval}`); + return; + } + + const totalCount = await FlagValueRepository.$getTotalBlocksIndexedByBucketSize(bucketSize === 1 ? 1008 : bucketSize) ?? tip - tail; + + const startHeight = presets[interval].retentionSpan !== -1 ? (tip - presets[interval].retentionSpan) : -1; + const txsCount = await FlagValueRepository.$queryTxCountBasedOnMask(mask, bucketSize, op, startHeight); + res.header('X-total-count', totalCount.toString()); + res.header('Expires', new Date(Date.now() + 1000 * 3600 * 24 * (presets[interval].bucketSizes[0] / 144)).toUTCString()); + res.send(txsCount); + } catch (e: any) { + handleError(req, res, 400, e instanceof Error ? e.message : 'Failed to get flag values'); + } + } + private async $postTransaction(req: Request, res: Response) { res.setHeader('content-type', 'text/plain'); try { diff --git a/backend/src/api/bitcoin/electrum-api.ts b/backend/src/api/bitcoin/electrum-api.ts index ce8ad3cbb..bf9a28c5d 100644 --- a/backend/src/api/bitcoin/electrum-api.ts +++ b/backend/src/api/bitcoin/electrum-api.ts @@ -5,7 +5,7 @@ import { IEsploraApi } from './esplora-api.interface'; import { IElectrumApi } from './electrum-api.interface'; import BitcoinApi from './bitcoin-api'; import logger from '../../logger'; -import crypto from "crypto-js"; +import crypto from 'crypto-js'; import loadingIndicators from '../loading-indicators'; import memoryCache from '../memory-cache'; @@ -40,26 +40,11 @@ class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi { }); } + /** @asyncUnsafe */ async $getAddress(address: string): Promise { const addressInfo = await this.bitcoindClient.validateAddress(address); if (!addressInfo || !addressInfo.isvalid) { - return ({ - 'address': address, - 'chain_stats': { - 'funded_txo_count': 0, - 'funded_txo_sum': 0, - 'spent_txo_count': 0, - 'spent_txo_sum': 0, - 'tx_count': 0 - }, - 'mempool_stats': { - 'funded_txo_count': 0, - 'funded_txo_sum': 0, - 'spent_txo_count': 0, - 'spent_txo_sum': 0, - 'tx_count': 0 - } - }); + throw new Error('Invalid Bitcoin address'); } try { @@ -91,10 +76,11 @@ class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi { } } + /** @asyncUnsafe */ async $getAddressTransactions(address: string, lastSeenTxId: string): Promise { const addressInfo = await this.bitcoindClient.validateAddress(address); if (!addressInfo || !addressInfo.isvalid) { - return []; + throw new Error('Invalid Bitcoin address'); } try { @@ -160,10 +146,11 @@ class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi { } } + /** @asyncUnsafe */ async $getAddressUtxos(address: string): Promise { const addressInfo = await this.bitcoindClient.validateAddress(address); if (!addressInfo || !addressInfo.isvalid) { - return []; + throw new Error('Invalid Bitcoin address'); } const scripthash = this.encodeScriptHash(addressInfo.scriptPubKey); return this.$getScriptHashUtxos(scripthash); @@ -206,10 +193,11 @@ class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi { } } + /** @asyncUnsafe */ async $getScriptHashUtxos(scripthash: string): Promise { const utxos = await this.$getScriptHashUnspent(scripthash); const result: IEsploraApi.UTXO[] = []; - for(let utxo of utxos) { + for(const utxo of utxos) { if(utxo.height===0) { //Unconfirmed result.push({ @@ -244,6 +232,7 @@ class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi { return this.electrumClient.blockchainScripthash_listunspent(scriptHash); } + /** @asyncUnsafe */ async $getTransactionMerkleProof(txId: string): Promise { const tx = await this.$getRawTransaction(txId); return this.electrumClient.blockchainTransaction_getMerkle(txId, tx.status.block_height); diff --git a/backend/src/api/bitcoin/esplora-api.ts b/backend/src/api/bitcoin/esplora-api.ts index d7be065fa..e15a703a2 100644 --- a/backend/src/api/bitcoin/esplora-api.ts +++ b/backend/src/api/bitcoin/esplora-api.ts @@ -28,14 +28,22 @@ interface FailoverHost { hybrid?: string, backend?: string, electrs?: string, + ssr?: string, + core?: string, + os?: string, lastUpdated: number, + }, + liquidAudit?: { + pegRatio: number, + bitcoinLastBlockUpdate: number, + liquidLastBlockUpdate: number, } } class FailoverRouter { activeHost: FailoverHost; fallbackHost: FailoverHost; - maxSlippage: number = config.ESPLORA.MAX_BEHIND_TIP ?? 2; + maxSlippage: number = config.ESPLORA.MAX_BEHIND_TIP ?? (Common.isLiquid() ? 8 : 2); maxHeight: number = 0; hosts: FailoverHost[]; multihost: boolean; @@ -98,11 +106,12 @@ class FailoverRouter { }); if (this.multihost) { - this.pollHosts(); + void this.pollHosts(); } } // start polling hosts to measure availability & rtt + /** @asyncSafe */ private async pollHosts(): Promise { if (this.pollTimer) { clearTimeout(this.pollTimer); @@ -141,11 +150,14 @@ class FailoverRouter { } } + await this.$updateLiquidAudit(host); + // Check front and backend git hashes less often if (Date.now() - host.hashes.lastUpdated > this.gitHashInterval) { await Promise.all([ this.$updateFrontendGitHash(host), - this.$updateBackendGitHash(host), + this.$updateBackendVersions(host), + this.$updateSSRGitHash(host), config.MEMPOOL.OFFICIAL ? this.$updateHybridGitHash(host) : Promise.resolve(), ]); host.hashes.lastUpdated = Date.now(); @@ -191,7 +203,7 @@ class FailoverRouter { const elapsed = Date.now() - start; - this.pollTimer = setTimeout(() => { this.pollHosts(); }, Math.max(1, this.pollInterval - elapsed)); + this.pollTimer = setTimeout(() => { void this.pollHosts(); }, Math.max(1, this.pollInterval - elapsed)); } private formatRanking(index: number, host: FailoverHost, active: FailoverHost, maxHeight: number): string { @@ -250,7 +262,12 @@ class FailoverRouter { private async $updateFrontendGitHash(host: FailoverHost): Promise { try { const url = `${host.publicDomain}/resources/config.js`; - const response = await this.pollConnection.get(url, { timeout: config.ESPLORA.FALLBACK_TIMEOUT }); + const response = await this.pollConnection.get( + url, { + timeout: config.ESPLORA.FALLBACK_TIMEOUT, + headers: Common.isLiquid() ? { 'Host': 'liquid.network' } : undefined + } + ); const match = response.data.match(/GIT_COMMIT_HASH\s*=\s*['"](.*?)['"]/); if (match && match[1]?.length) { host.hashes.frontend = match[1]; @@ -273,7 +290,7 @@ class FailoverRouter { path: '/en-US/resources/config.js', method: 'GET', headers: { - 'Host': 'mempool.space' + 'Host': Common.isLiquid() ? 'liquid.network' : 'mempool.space' }, timeout: config.ESPLORA.FALLBACK_TIMEOUT, }, (res) => { @@ -301,18 +318,83 @@ class FailoverRouter { } } - private async $updateBackendGitHash(host: FailoverHost): Promise { + private async $updateBackendVersions(host: FailoverHost): Promise { try { const url = `${host.publicDomain}/api/v1/backend-info`; - const response = await this.pollConnection.get(url, { timeout: config.ESPLORA.FALLBACK_TIMEOUT }); + const response = await this.pollConnection.get( + url, { + timeout: config.ESPLORA.FALLBACK_TIMEOUT, + headers: Common.isLiquid() ? { 'Host': 'liquid.network' } : undefined + } + ); if (response.data?.gitCommit) { host.hashes.backend = response.data.gitCommit; } + if (response.data?.coreVersion) { + host.hashes.core = response.data.coreVersion; + } + if (response.data?.osVersion) { + host.hashes.os = response.data.osVersion; + } } catch (e) { // failed to get backend build hash - do nothing } } + private async $updateSSRGitHash(host: FailoverHost): Promise { + try { + const url = `${host.publicDomain}/ssr/api/status`; + const response = await this.pollConnection.get( + url, { + timeout: config.ESPLORA.FALLBACK_TIMEOUT, + headers: Common.isLiquid() ? { 'Host': 'liquid.network' } : undefined + } + ); + if (response.data?.gitHash) { + host.hashes.ssr = response.data.gitHash; + } + } catch (e) { + // failed to get ssr build hash - do nothing + } + } + + private async $updateLiquidAudit(host: FailoverHost): Promise { + if (config.MEMPOOL.NETWORK !== 'liquid') { + return; + } + try { + const [reservesResponse, pegsResponse] = await Promise.all([ + this.pollConnection.get( + `${host.publicDomain}/api/v1/liquid/reserves`, { + timeout: config.ESPLORA.FALLBACK_TIMEOUT, + headers: { 'Host': 'liquid.network' } + } + ), + this.pollConnection.get( + `${host.publicDomain}/api/v1/liquid/pegs`, { + timeout: config.ESPLORA.FALLBACK_TIMEOUT, + headers: { 'Host': 'liquid.network' } + } + ), + ]); + + const reservesAmount = Number(reservesResponse.data?.amount); + const pegsAmount = Number(pegsResponse.data?.amount); + const bitcoinLastBlockUpdate = Number(reservesResponse.data?.lastBlockUpdate); + const liquidLastBlockUpdate = Number(pegsResponse.data?.lastBlockUpdate); + + if (Number.isFinite(reservesAmount) && Number.isFinite(pegsAmount) && Number.isFinite(bitcoinLastBlockUpdate) && Number.isFinite(liquidLastBlockUpdate) && pegsAmount > 0) { + host.liquidAudit = { + pegRatio: (reservesAmount / pegsAmount) * 100, + bitcoinLastBlockUpdate, + liquidLastBlockUpdate, + }; + } + } catch (e) { + // failed to get liquid audit values - do nothing + } + } + // returns the public mempool domain corresponding to an esplora server url // (a bit of a hack to avoid manually specifying frontend & backend URLs for each esplora server) private extractPublicDomain(url: string): string { @@ -409,6 +491,7 @@ class ElectrsApi implements AbstractBitcoinApi { return this.failoverRouter.$get('/blocks/tip/hash'); } + /** @asyncUnsafe */ async $getTxIdsForBlock(hash: string, fallbackToCore = false): Promise { try { const txids = await this.failoverRouter.$get('/block/' + hash + '/txids'); @@ -425,6 +508,7 @@ class ElectrsApi implements AbstractBitcoinApi { } } + /** @asyncUnsafe */ async $getTxsForBlock(hash: string, fallbackToCore = false): Promise { try { const txs = await this.failoverRouter.$get('/internal/block/' + hash + '/txs'); @@ -518,6 +602,7 @@ class ElectrsApi implements AbstractBitcoinApi { return this.failoverRouter.$post('/internal/txs/outspends/by-outpoint', outpoints.map(out => `${out.txid}:${out.vout}`), 'json'); } + /** @asyncUnsafe */ async $getCoinbaseTx(blockhash: string): Promise { const txid = await this.failoverRouter.$get(`/block/${blockhash}/txid/0`); return this.failoverRouter.$get('/tx/' + txid); @@ -544,6 +629,7 @@ class ElectrsApi implements AbstractBitcoinApi { checked: !!host.checked, lastChecked: host.lastChecked || 0, hashes: host.hashes, + ...(config.MEMPOOL.NETWORK === 'liquid' ? { liquidAudit: host.liquidAudit } : {}), })); } else { return []; diff --git a/backend/src/api/block-processor.ts b/backend/src/api/block-processor.ts new file mode 100644 index 000000000..6556e907e --- /dev/null +++ b/backend/src/api/block-processor.ts @@ -0,0 +1,262 @@ +import config from '../config'; +import logger from '../logger'; +import { + BlockExtended, + BlockSummary, + PoolTag, + MempoolTransactionExtended, + CpfpSummary, + TemplateAlgorithm, + MempoolBlockWithTransactions, +} from '../mempool.interfaces'; +import { IEsploraApi } from './bitcoin/esplora-api.interface'; +import { Acceleration } from './services/acceleration'; +import { calculateGoodBlockCpfp, calculateClusterMempoolBlockCpfp, calculateFastBlockCpfp, BlockCpfpData } from './cpfp'; +import mempoolBlocks from './mempool-blocks'; +import memPool from './mempool'; +import Audit, { AuditResult } from './audit'; +import blocks from './blocks'; +import transactionUtils from './transaction-utils'; +import { ClusterMempool } from '../cluster-mempool/cluster-mempool'; +import { Common } from './common'; +import accelerationApi from './services/acceleration'; + +interface ProcessedAudit extends AuditResult { + expectedFees: number; + expectedWeight: number; + projectedBlocks: MempoolBlockWithTransactions[]; +} + +export interface BlockProcessingResult { + templateAlgorithm: TemplateAlgorithm; + cpfpSummary: CpfpSummary; + blockExtended: BlockExtended; + blockSummary: BlockSummary; + auditResult?: ProcessedAudit; +} + +const CM_ACTIVATION_HEIGHT: { [network: string]: number } = { + 'mainnet': 940000, + 'testnet': 4860000, + 'testnet4': 125000, + 'signet': 294000, + 'regtest': 0, +}; + +class BlockProcessor { + + /** @asyncUnsafe */ + public async $processNewBlock( + block: IEsploraApi.Block, + transactions: MempoolTransactionExtended[], + pool: PoolTag, + accelerations: Record + ): Promise { + const poolAccelerations = Object.values(accelerations) + .filter(a => a.pools.includes(pool.uniqueId)) + .map(a => ({ txid: a.txid, max_bid: a.feeDelta })); + + const { templateAlgorithm, cpfpSummary } = detectTemplateAlgorithm( + block.height, + transactions, + poolAccelerations + ); + + + const blockExtended = await blocks.$getBlockExtended(block, cpfpSummary.transactions, pool); + const blockSummary = blocks.summarizeBlockTransactions(block.id, block.height, cpfpSummary.transactions); + + let auditResult: ProcessedAudit | undefined; + if (config.MEMPOOL.AUDIT && memPool.isInSync()) { + auditResult = await this.$runAudit( + blockExtended, + transactions, + templateAlgorithm, + pool, + accelerations + ); + + if (blockExtended.extras) { + blockExtended.extras.matchRate = auditResult.matchRate; + blockExtended.extras.expectedFees = auditResult.expectedFees; + blockExtended.extras.expectedWeight = auditResult.expectedWeight; + blockExtended.extras.similarity = auditResult.similarity; + } + } else if (blockExtended.extras) { + const mBlocks = mempoolBlocks.getMempoolBlocksWithTransactions(); + if (mBlocks?.length && mBlocks[0].transactions) { + blockExtended.extras.similarity = Common.getSimilarity(mBlocks[0], transactions); + } + } + + return { + templateAlgorithm, + cpfpSummary, + blockExtended, + blockSummary, + auditResult, + }; + } + + private async $runAudit( + block: BlockExtended, + transactions: MempoolTransactionExtended[], + templateAlgorithm: TemplateAlgorithm, + pool: PoolTag, + accelerations: Record + ): Promise { + const auditMempool = memPool.getMempool(); + const isAccelerated = accelerationApi.isAcceleratedBlock(block, Object.values(accelerations)); + + const candidateTxs = memPool.getMempoolCandidates(); + const candidates = (memPool.limitGBT && candidateTxs) + ? { txs: candidateTxs, added: [], removed: [] } + : undefined; + const transactionIds = (memPool.limitGBT) + ? Object.keys(candidates?.txs || {}) + : Object.keys(auditMempool); + + let projectedBlocks: MempoolBlockWithTransactions[]; + + if (templateAlgorithm === TemplateAlgorithm.clusterMempool) { + const clusterMempool = memPool.clusterMempool ?? new ClusterMempool(auditMempool, accelerations, true, 75000); + const cmBlocks = clusterMempool.getBlocks(config.MEMPOOL.MEMPOOL_BLOCKS_AMOUNT) ?? []; + projectedBlocks = mempoolBlocks.processClusterMempoolBlocks( + cmBlocks, + auditMempool, + accelerations, + false, + pool.uniqueId + ); + } else if (config.MEMPOOL.RUST_GBT) { + const added = memPool.limitGBT ? (candidates?.added || []) : []; + const removed = memPool.limitGBT ? (candidates?.removed || []) : []; + projectedBlocks = await mempoolBlocks.$rustUpdateBlockTemplates( + transactionIds, + auditMempool, + added, + removed, + candidates, + isAccelerated, + pool.uniqueId, + true + ); + } else { + projectedBlocks = await mempoolBlocks.$makeBlockTemplates( + transactionIds, + auditMempool, + candidates, + false, + isAccelerated, + pool.uniqueId + ); + } + + const auditResult = Audit.auditBlock(block.height, transactions, projectedBlocks, auditMempool); + + const stripped = projectedBlocks[0]?.transactions ? projectedBlocks[0].transactions : []; + + let totalFees = 0; + let totalWeight = 0; + for (const tx of stripped) { + totalFees += tx.fee; + totalWeight += (tx.vsize * 4); + } + + return { + ...auditResult, + expectedFees: totalFees, + expectedWeight: totalWeight, + projectedBlocks, + }; + } +} + +function saveCpfpDataToTransactions(transactions: MempoolTransactionExtended[], cpfpData: BlockCpfpData): void { + for (const tx of transactions) { + if (cpfpData.txs[tx.txid]) { + Object.assign(tx, cpfpData.txs[tx.txid]); + } + } +} + +export function saveCpfpDataToCpfpSummary(transactions: MempoolTransactionExtended[], cpfpData: BlockCpfpData): CpfpSummary { + saveCpfpDataToTransactions(transactions, cpfpData); + return { + transactions, + clusters: cpfpData.clusters, + version: cpfpData.version, + }; +} + +/** + * + * @param height + * @param blockTransactions + * @param poolAccelerations + * @param fast + * + * saves effective fee rates from detected algorithm to blockTransactions + */ +export function detectTemplateAlgorithm( + height: number, + blockTransactions: MempoolTransactionExtended[], + poolAccelerations: { txid: string; max_bid: number }[], + fast: boolean = false +): { templateAlgorithm: TemplateAlgorithm; cpfpSummary: CpfpSummary } { + + const legacyCpfpData = fast ? calculateFastBlockCpfp( + height, + blockTransactions, + ) : calculateGoodBlockCpfp( + height, + blockTransactions, + poolAccelerations + ); + + if (!config.MEMPOOL.CLUSTER_MEMPOOL_INDEXING) { + return { + templateAlgorithm: TemplateAlgorithm.legacy, + cpfpSummary: saveCpfpDataToCpfpSummary(blockTransactions, legacyCpfpData), + }; + } + + const network = config.MEMPOOL.NETWORK || 'mainnet'; + const activationHeight = CM_ACTIVATION_HEIGHT[network] ?? Infinity; + + if (height < activationHeight) { + return { + templateAlgorithm: TemplateAlgorithm.legacy, + cpfpSummary: saveCpfpDataToCpfpSummary(blockTransactions, legacyCpfpData), + }; + } + + const clusterCpfpData = calculateClusterMempoolBlockCpfp( + height, + blockTransactions, + poolAccelerations + ); + + const clusterTxs = blockTransactions.map(tx => ({ txid: tx.txid, rate: clusterCpfpData.txs[tx.txid].effectiveFeePerVsize ?? tx.effectiveFeePerVsize })); + const legacyTxs = blockTransactions.map(tx => ({ txid: tx.txid, rate: legacyCpfpData.txs[tx.txid].effectiveFeePerVsize ?? tx.effectiveFeePerVsize })); + const clusterPrioritization = transactionUtils.identifyPrioritizedTransactions(clusterTxs, 'rate'); + const legacyPrioritization = transactionUtils.identifyPrioritizedTransactions(legacyTxs, 'rate'); + + const clusterCount = clusterPrioritization.prioritized.length + clusterPrioritization.deprioritized.length; + const legacyCount = legacyPrioritization.prioritized.length + legacyPrioritization.deprioritized.length; + + if (clusterCount < legacyCount) { + saveCpfpDataToTransactions(blockTransactions, clusterCpfpData); + return { + templateAlgorithm: TemplateAlgorithm.clusterMempool, + cpfpSummary: saveCpfpDataToCpfpSummary(blockTransactions, clusterCpfpData), + }; + } else { + return { + templateAlgorithm: TemplateAlgorithm.legacy, + cpfpSummary: saveCpfpDataToCpfpSummary(blockTransactions, legacyCpfpData), + }; + } +} + +export default new BlockProcessor(); diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index d33e8fda6..97276a922 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -2,7 +2,7 @@ import config from '../config'; import bitcoinApi, { bitcoinCoreApi } from './bitcoin/bitcoin-api-factory'; import logger from '../logger'; import memPool from './mempool'; -import { BlockExtended, BlockExtension, BlockSummary, PoolTag, TransactionExtended, TransactionMinerInfo, CpfpSummary, MempoolTransactionExtended, TransactionClassified, BlockAudit, TransactionAudit } from '../mempool.interfaces'; +import { BlockExtended, BlockExtension, BlockSummary, PoolTag, TransactionExtended, TransactionMinerInfo, CpfpSummary, MempoolTransactionExtended, TransactionClassified, BlockAudit, TransactionAudit, TemplateAlgorithm } from '../mempool.interfaces'; import { Common } from './common'; import diskCache from './disk-cache'; import transactionUtils from './transaction-utils'; @@ -28,13 +28,19 @@ import chainTips from './chain-tips'; import websocketHandler from './websocket-handler'; import redisCache from './redis-cache'; import rbfCache from './rbf-cache'; +import bitcoinSecondClient from './bitcoin/bitcoin-second-client'; +import mempoolBlocks from './mempool-blocks'; +import statistics from './statistics/statistics'; import { calcBitsDifference } from './difficulty-adjustment'; import AccelerationRepository from '../repositories/AccelerationRepository'; -import { calculateFastBlockCpfp, calculateGoodBlockCpfp } from './cpfp'; +import { calculateGoodBlockCpfp } from './cpfp'; +import blockProcessor, { BlockProcessingResult, detectTemplateAlgorithm, saveCpfpDataToCpfpSummary } from './block-processor'; import mempool from './mempool'; import CpfpRepository from '../repositories/CpfpRepository'; -import { parseDATUMTemplateCreator } from '../utils/bitcoin-script'; +import { parseDATUMTemplateCreator, parseDMNDTemplateCreator } from '../utils/bitcoin-script'; import database from '../database'; +import { getBlockFirstSeenFromLogs, getOldestLogTimestampFromLogs, scanLogsForBlocksFirstSeen } from '../utils/file-read'; +import FlagValueRepository, { INDEXING_PRESETS } from '../repositories/FlagValueRepository'; class Blocks { private blocks: BlockExtended[] = []; @@ -45,10 +51,12 @@ class Blocks { private previousDifficultyRetarget = 0; private quarterEpochBlockTime: number | null = null; private newBlockCallbacks: ((block: BlockExtended, txIds: string[], transactions: TransactionExtended[]) => void)[] = []; - private newAsyncBlockCallbacks: ((block: BlockExtended, txIds: string[], transactions: MempoolTransactionExtended[]) => Promise)[] = []; private classifyingBlocks: boolean = false; + private oldestCoreLogTimestamp: number | undefined | null = undefined; private mainLoopTimeout: number = 120000; + private indexingFlagValues: boolean = false; + private flagValuesDeleteQueue: number[]= []; constructor() { } @@ -72,10 +80,6 @@ class Blocks { this.newBlockCallbacks.push(fn); } - public setNewAsyncBlockCallback(fn: (block: BlockExtended, txIds: string[], transactions: MempoolTransactionExtended[]) => Promise) { - this.newAsyncBlockCallbacks.push(fn); - } - /** * Return the list of transaction for a block * @param blockHash @@ -85,6 +89,8 @@ class Blocks { * @param quiet - don't print non-essential logs * @param addMempoolData - calculate sigops etc * @returns Promise + * + * @asyncUnsafe */ private async $getTransactionsExtended( blockHash: string, @@ -106,7 +112,7 @@ class Blocks { const mempool = memPool.getMempool(); let foundInMempool = 0; let totalFound = 0; - let missing = 0; + const missing = 0; // Copy existing transactions from the mempool if (!onlyCoinbase) { @@ -245,8 +251,10 @@ class Blocks { * @param block * @param transactions * @returns BlockExtended + * + * @asyncUnsafe */ - private async $getBlockExtended(block: IEsploraApi.Block, transactions: TransactionExtended[]): Promise { + public async $getBlockExtended(block: IEsploraApi.Block, transactions: TransactionExtended[], providedPool?: PoolTag): Promise { const coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]); const blk: Partial = Object.assign({}, block); @@ -327,9 +335,11 @@ class Blocks { extras.totalInputAmt = null; } - if (['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK)) { + if (['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) { let pool: PoolTag; - if (coinbaseTx !== undefined) { + if (providedPool) { + pool = providedPool; + } else if (coinbaseTx !== undefined) { pool = await this.$findBlockMiner(coinbaseTx); } else { if (config.DATABASE.ENABLED === true) { @@ -352,6 +362,8 @@ class Blocks { if (extras.pool.name === 'OCEAN') { extras.pool.minerNames = parseDATUMTemplateCreator(extras.coinbaseRaw); + } else if (extras.pool.name === 'DMND') { + extras.pool.minerNames = parseDMNDTemplateCreator(extras.coinbaseRaw); } } @@ -366,13 +378,21 @@ class Blocks { extras.expectedWeight = auditScore.expectedWeight; } } + + extras.firstSeen = null; + if (config.CORE_RPC.DEBUG_LOG_PATH) { + const oldestLog = this.getOldestCoreLogTimestamp(); + if (oldestLog) { + extras.firstSeen = getBlockFirstSeenFromLogs(block.id, block.timestamp, oldestLog); + } + } } blk.extras = extras; return blk; } - private async $getBlockStats(block: IEsploraApi.Block, transactions: TransactionExtended[]): Promise { + public async $getBlockStats(block: IEsploraApi.Block, transactions: TransactionExtended[]): Promise { if (!block.stale) { return bitcoinClient.getBlockStats(block.id); } @@ -449,6 +469,8 @@ class Blocks { * Try to find which miner found the block * @param txMinerInfo * @returns + * + * @asyncUnsafe */ private async $findBlockMiner(txMinerInfo: TransactionMinerInfo | undefined): Promise { if (txMinerInfo === undefined || txMinerInfo.vout.length < 1) { @@ -480,6 +502,129 @@ class Blocks { } } + /** @asyncUnsafe */ + private async $applyBlockTransactionsToMempool( + txIds: string[], + transactions: MempoolTransactionExtended[] + ): Promise<{ rbfTransactions: { [txid: string]: { replaced: MempoolTransactionExtended[], replacedBy: TransactionExtended }}}> { + const _memPool = memPool.getMempool(); + + const rbfTransactions = Common.findMinedRbfTransactions(transactions, memPool.getSpendMap()); + memPool.handleRbfTransactions(rbfTransactions); + memPool.removeFromSpendMap(transactions); + + if (config.MEMPOOL.CLUSTER_MEMPOOL) { + memPool.clusterMempool?.applyMempoolChange({ + added: [], + removed: transactions, + accelerations: mempool.getAccelerations(), + }); + } + + for (const txId of txIds) { + delete _memPool[txId]; + rbfCache.mined(txId); + } + redisCache.queueTransactionsForRemoval(txIds); + + let candidates; + let transactionIds: string[]; + + if (memPool.limitGBT) { + const minFeeMempool = await bitcoinSecondClient.getRawMemPool(); + const minFeeTip = await bitcoinSecondClient.getBlockCount(); + candidates = memPool.getNextCandidates(minFeeMempool, minFeeTip, transactions); + transactionIds = Object.keys(candidates?.txs || {}); + } else { + candidates = undefined; + transactionIds = Object.keys(memPool.getMempool()); + } + + if (config.MEMPOOL.CLUSTER_MEMPOOL) { + const cmBlocks = mempool.clusterMempool?.getBlocks(config.MEMPOOL.MEMPOOL_BLOCKS_AMOUNT) ?? []; + mempoolBlocks.processClusterMempoolBlocks(cmBlocks, _memPool, mempool.getAccelerations()); + } else if (config.MEMPOOL.RUST_GBT) { + const added = memPool.limitGBT ? (candidates?.added || []) : []; + const removed = memPool.limitGBT ? (candidates?.removed || []) : transactions; + await mempoolBlocks.$rustUpdateBlockTemplates(transactionIds, _memPool, added, removed, candidates, true); + } else { + await mempoolBlocks.$makeBlockTemplates(transactionIds, _memPool, candidates, true, true); + } + + return { rbfTransactions }; + } + + /** @asyncUnsafe */ + private async $saveBlockData( + processingResult: BlockProcessingResult, + timer: number + ): Promise { + const blockExtended = processingResult.blockExtended; + const cpfpSummary = processingResult.cpfpSummary; + + let latestPriceId; + try { + latestPriceId = await PricesRepository.$getLatestPriceId(); + this.updateTimerProgress(timer, `got latest price id ${this.currentBlockHeight}`); + } catch (e) { + logger.debug('failed to fetch latest price id from db: ' + (e instanceof Error ? e.message : e)); + } + if (priceUpdater.historyInserted === true && latestPriceId !== null) { + await blocksRepository.$saveBlockPrices([{ + height: blockExtended.height, + priceId: latestPriceId, + }]); + this.updateTimerProgress(timer, `saved prices for ${this.currentBlockHeight}`); + } else { + logger.debug(`Cannot save block price for ${blockExtended.height} because the price updater hasnt completed yet. Trying again in 10 seconds.`, logger.tags.mining); + indexer.scheduleSingleTask('blocksPrices', 10000); + } + + if (Common.blocksSummariesIndexingEnabled() === true) { + // indexes the summary as a side effect + await this.$getStrippedBlockTransactions(blockExtended.id, true, false, cpfpSummary, blockExtended.height); + this.updateTimerProgress(timer, `saved block summary for ${this.currentBlockHeight}`); + } + + if (config.MEMPOOL.CPFP_INDEXING) { + // can be slow, and isn't critical, so don't await + void this.$saveCpfp(blockExtended.id, this.currentBlockHeight, cpfpSummary); + this.updateTimerProgress(timer, `saved cpfp for ${this.currentBlockHeight}`); + } + + if (processingResult.auditResult) { + void BlocksSummariesRepository.$saveTemplate({ + height: blockExtended.height, + template: { + id: blockExtended.id, + transactions: processingResult.auditResult.projectedBlocks[0].transactions, + }, + version: 1, + }); + this.updateTimerProgress(timer, `saved audit template for ${this.currentBlockHeight}`); + + void BlocksAuditsRepository.$saveAudit({ + version: 1, + templateAlgorithm: processingResult.templateAlgorithm, + time: blockExtended.timestamp, + height: blockExtended.height, + hash: blockExtended.id, + unseenTxs: processingResult.auditResult.unseen, + addedTxs: processingResult.auditResult.added, + prioritizedTxs: processingResult.auditResult.prioritized, + missingTxs: processingResult.auditResult.censored, + freshTxs: processingResult.auditResult.fresh, + sigopTxs: processingResult.auditResult.sigop, + fullrbfTxs: processingResult.auditResult.fullrbf, + acceleratedTxs: processingResult.auditResult.accelerated, + matchRate: processingResult.auditResult.matchRate, + expectedFees: processingResult.auditResult.expectedFees, + expectedWeight: processingResult.auditResult.expectedWeight, + }); + this.updateTimerProgress(timer, `saved audit results for ${this.currentBlockHeight}`); + } + } + /** * [INDEXING] Index all blocks summaries for the block txs visualization */ @@ -547,6 +692,156 @@ class Blocks { } } + /** + * [INDEXING] Index all blocks flag values for the goggles graph rendering + * + * @asyncSafe + */ + public async $generateFlagValuesDatabase(): Promise { + const MAX_BLOCKS_PERQUERY = 144; + if (this.indexingFlagValues) { + return; + } + + if (Common.blocksSummariesIndexingEnabled() === false || Common.isLiquid()) { + return; + } + + this.indexingFlagValues = true; + + const tipOfSummaries = await BlocksSummariesRepository.$getTipIndexed(); + if (!tipOfSummaries) { + this.indexingFlagValues = false; + return; + } + + let newlyIndexedBuckets = 0; + + while (this.flagValuesDeleteQueue.length > 0) { // Deletion of in-queue heights due to reorg + const deletionHeight = this.flagValuesDeleteQueue.shift(); + if (deletionHeight === undefined) { + continue; + } + await FlagValueRepository.$deleteFlagValuesFromHeight(deletionHeight); + } + + for (const preset of INDEXING_PRESETS) { + let seedHeight = preset.retentionSpan > -1 ? tipOfSummaries - preset.retentionSpan : 0; + if (config.MEMPOOL.INDEXING_BLOCKS_AMOUNT > 0) { + seedHeight = Math.max(seedHeight, tipOfSummaries - config.MEMPOOL.INDEXING_BLOCKS_AMOUNT + 1); + } + const firstBucket = Math.floor((tipOfSummaries + 1) / preset.bucketSize) * preset.bucketSize - preset.bucketSize; + const lastBucket = Math.max(0, Math.floor(seedHeight / preset.bucketSize) * preset.bucketSize); + + // Deletion of flag values out of retention span + const tipAndTailOfFlagValues = await FlagValueRepository.$getTipAndTailIndexedByBucketSize(preset.bucketSize); + if (tipAndTailOfFlagValues && lastBucket > tipAndTailOfFlagValues.tail) { // Drop buckets that fell out of block span + logger.debug(`Deleting all the flag values ${preset.name} below height #${lastBucket}`, logger.tags.goggles); + await FlagValueRepository.$deleteFlagValuesBelowHeight(lastBucket, preset.bucketSize); + } + + if (firstBucket < lastBucket) { + continue; // no complete bucket in range + } + + const indexedBuckets = await FlagValueRepository.$getIndexedStartHeights(preset.bucketSize, firstBucket, lastBucket); + const isBucketIndexed = {}; + // We map the buckets that are already indexed to skip them + for (const startHeight of indexedBuckets) { + isBucketIndexed[startHeight] = true; + } + + logger.debug(`Processing and indexing flag values from #${firstBucket} to #${lastBucket} ${preset.name}`, logger.tags.goggles); + + let timer = Date.now() / 1000; + const startedAt = Date.now() / 1000; + let blocksComputedInTotal = 0; + let blocksComputedThisRun = 0; + const blocksToCompute = firstBucket + preset.bucketSize - lastBucket - (indexedBuckets.length * preset.bucketSize); + for (let bucketStart = firstBucket; bucketStart >= lastBucket; bucketStart -= preset.bucketSize) { + if (isBucketIndexed[bucketStart]) { + continue; // already indexed + } + try { + const bucketFirstHeight = bucketStart + preset.bucketSize - 1; + const bucketLastHeight = bucketStart - 1; + + let step = bucketFirstHeight; + + const dataPerFlag: Record> = {}; + let sumTimestamps = 0; + let nBlocks = 0; + let incomplete = false; + + // Incrementalized logic capped by max blocks per query, not bucket size + while (step > bucketLastHeight) { + const blocksPerQuery = Math.min(step - bucketLastHeight, MAX_BLOCKS_PERQUERY); + const cappedLastHeight = step - blocksPerQuery; + + const blocks = await BlocksSummariesRepository.$getSummariesBetweenHeights(step, cappedLastHeight); + await Common.sleep$(250); // Don't query/index flag values too fast + + if (!blocks || blocks.length < blocksPerQuery) { + incomplete = true; + break; // Incomplete bucket + } + + // Flag values processing + for (const block of blocks) { + const txData = JSON.parse(block.transactions).map((tx) => ({flags: tx.flags, vsize: tx.vsize})); + for (const data of txData) { + if (dataPerFlag[data.flags] === undefined || Object.keys(dataPerFlag[data.flags]).length === 0) { + dataPerFlag[data.flags] = { + txCount: 0, + vSizeTotal: 0 + }; + } + dataPerFlag[data.flags].txCount = dataPerFlag[data.flags].txCount + 1; + dataPerFlag[data.flags].vSizeTotal = dataPerFlag[data.flags].vSizeTotal + data.vsize; + } + sumTimestamps += block.timestamp; + blocksComputedInTotal++; + blocksComputedThisRun++; + nBlocks++; + } + + // Logging + const elapsedSeconds = (Date.now() / 1000) - timer; + if (elapsedSeconds > 5) { + const runningFor = (Date.now() / 1000) - startedAt; + const blocksPerSecond = blocksComputedThisRun / elapsedSeconds; + const completion = (blocksComputedInTotal / blocksToCompute) * 100; + logger.debug(`Indexing flag values ${preset.name} | ${blocksComputedInTotal}/${blocksToCompute} (${completion.toFixed(2)}%) | ~${blocksPerSecond.toFixed(2)} blocks/sec | elapsed: ${runningFor.toFixed(2)} seconds`,logger.tags.goggles); + timer = Date.now() / 1000; + blocksComputedThisRun = 0; + } + + step -= blocksPerQuery; + } + + if (incomplete) { + continue; + } + + const avgTimestamp = sumTimestamps / nBlocks; + await FlagValueRepository.$saveBatchFlagValues(preset.bucketSize, bucketStart, dataPerFlag, avgTimestamp); + nBlocks = 0; + newlyIndexedBuckets++; + } catch (e) { + logger.err(`Failed to index flag values between #${bucketStart} and #${bucketStart + preset.bucketSize - 1}. Reason: ${(e instanceof Error ? e.message : e)}`, logger.tags.goggles); + } + } + logger.debug(`Successfully indexed #${blocksComputedInTotal} blocks ${preset.name} in ${((Date.now() / 1000) - startedAt).toFixed(2)} seconds`, logger.tags.goggles); + } + if (newlyIndexedBuckets > 0) { + logger.notice(`Flag values indexing completed: indexed ${newlyIndexedBuckets} buckets`, logger.tags.goggles); + } else { + logger.debug(`Flag values indexing completed: indexed ${newlyIndexedBuckets} buckets`, logger.tags.goggles); + } + this.indexingFlagValues = false; + } + + /** @asyncUnsafe */ public async $indexBlockSummary(hash: string, height: number, stale?: boolean): Promise { if (config.MEMPOOL.BACKEND === 'esplora') { const txs = (await bitcoinApi.$getTxsForBlock(hash, stale)).map(tx => transactionUtils.extendMempoolTransaction(tx)); @@ -610,6 +905,8 @@ class Blocks { /** * [INDEXING] Index expected fees & weight for all audited blocks + * + * @asyncUnsafe */ public async $generateAuditStats(): Promise { const blockIds = await BlocksAuditsRepository.$getBlocksWithoutSummaries(); @@ -650,6 +947,8 @@ class Blocks { /** * [INDEXING] Index transaction classification flags for Goggles + * + * @asyncSafe */ public async $classifyBlocks(): Promise { if (this.classifyingBlocks) { @@ -707,7 +1006,8 @@ class Blocks { // fetch transactions txs = (await bitcoinApi.$getTxsForBlock(blockHash, true)).map(tx => transactionUtils.extendMempoolTransaction(tx)) || []; // add CPFP - const cpfpSummary = calculateGoodBlockCpfp(height, txs, []); + const blockCpfpData = calculateGoodBlockCpfp(height, txs, []); + const cpfpSummary = saveCpfpDataToCpfpSummary(txs, blockCpfpData); // classify const { transactions: classifiedTxs } = this.summarizeBlockTransactions(blockHash, height, cpfpSummary.transactions); await BlocksSummariesRepository.$saveTransactions(height, blockHash, classifiedTxs, 2); @@ -744,7 +1044,8 @@ class Blocks { } templateTxs.push(tx || templateTx); } - const cpfpSummary = calculateGoodBlockCpfp(height, templateTxs?.filter(tx => tx['effectiveFeePerVsize'] != null) as MempoolTransactionExtended[], []); + const blockCpfpData = calculateGoodBlockCpfp(height, templateTxs?.filter(tx => tx['effectiveFeePerVsize'] != null) as MempoolTransactionExtended[], []); + const cpfpSummary = saveCpfpDataToCpfpSummary(templateTxs as MempoolTransactionExtended[], blockCpfpData); // classify const { transactions: classifiedTxs } = this.summarizeBlockTransactions(blockHash, height, cpfpSummary.transactions); const classifiedTxMap: { [txid: string]: TransactionClassified } = {}; @@ -830,6 +1131,7 @@ class Blocks { /** * [INDEXING] Index all blocks metadata for the mining dashboard + * @asyncSafe */ public async $generateBlockDatabase(): Promise { try { @@ -907,6 +1209,46 @@ class Blocks { return await BlocksRepository.$validateChain(); } + /** + * [INDEXING] Index all blocks first seen time from Bitcoin Core debug logs + * + * @asyncUnsafe + */ + public async $indexBlocksFirstSeen(): Promise { + const previous = this.oldestCoreLogTimestamp; + const oldestLogTimestamp = this.getOldestCoreLogTimestamp(true); + const hasLogFileChanged = previous !== undefined && oldestLogTimestamp !== previous; + + if (!oldestLogTimestamp) { + return; + } + + // If the log file changed since last run, re-try to index blocks marked with sentinel value + const blocks = await BlocksRepository.$getBlocksWithoutFirstSeen(hasLogFileChanged); + + if (!blocks?.length) { + return; + } + logger.debug(`Indexing ${blocks.length} block first seen times${hasLogFileChanged ? ' (log file changed since last run)' : ''}`); + const startedAt = Date.now(); + const results = scanLogsForBlocksFirstSeen(blocks, oldestLogTimestamp); + const foundCount = results.filter(result => result.firstSeen !== null).length; + logger.debug(`Found first seen times of ${foundCount} / ${results.length} blocks in Core logs, saving to database...`); + await BlocksRepository.$saveFirstSeenTimes(results); + + const blocksByHash = new Map(this.blocks.map<[string, BlockExtended]>(block => [block.id, block])); + + for (const { hash, firstSeen } of results) { + const cachedBlock = blocksByHash.get(hash); + if (cachedBlock?.extras) { + cachedBlock.extras.firstSeen = firstSeen; + } + } + + logger.debug(`Indexed ${foundCount} / ${blocks.length} block first seen times in ${((Date.now() - startedAt) / 1000).toFixed(2)} seconds`); + } + + /** @asyncUnsafe */ public async $updateBlocks(): Promise { // warn if this run stalls the main loop for more than 2 minutes const timer = this.startTimer(); @@ -992,59 +1334,55 @@ class Blocks { } } - let accelerations = Object.values(mempool.getAccelerations()); - if (accelerations?.length > 0) { - const pool = await this.$findBlockMiner(transactionUtils.stripCoinbaseTransaction(transactions[0])); - accelerations = accelerations.filter(a => a.pools.includes(pool.uniqueId)); - } - const cpfpSummary: CpfpSummary = calculateGoodBlockCpfp(block.height, transactions, accelerations.map(a => ({ txid: a.txid, max_bid: a.feeDelta }))); - const blockExtended: BlockExtended = await this.$getBlockExtended(block, cpfpSummary.transactions); - const blockSummary: BlockSummary = this.summarizeBlockTransactions(block.id, block.height, cpfpSummary.transactions); + const pool = await this.$findBlockMiner(transactionUtils.stripCoinbaseTransaction(transactions[0])); + const accelerations = mempool.getAccelerations(); + + const processingResult = await blockProcessor.$processNewBlock( + block, + transactions, + pool, + accelerations + ); + + const blockExtended = processingResult.blockExtended; + const blockSummary = processingResult.blockSummary; + const cpfpSummary = processingResult.cpfpSummary; this.updateTimerProgress(timer, `got block data for ${this.currentBlockHeight}`); - if (Common.indexingEnabled()) { - if (!fastForwarded) { - await this.$handleReorgs(blockExtended, timer); - } + if (config.STATISTICS.ENABLED && config.DATABASE.ENABLED) { + await statistics.runStatistics(); + } + const { rbfTransactions } = await this.$applyBlockTransactionsToMempool(txIds, cpfpSummary.transactions); + this.updateTimerProgress(timer, `applied mempool changes for ${this.currentBlockHeight}`); + + if (config.STATISTICS.ENABLED && config.DATABASE.ENABLED) { + await statistics.runStatistics(); + } + + if (Common.indexingEnabled() && !fastForwarded) { + await this.$handleReorgs(blockExtended, timer); + } + + await websocketHandler.handleNewBlock(blockExtended, txIds, cpfpSummary.transactions, rbfTransactions); + this.updateTimerProgress(timer, `sent websocket updates for ${this.currentBlockHeight}`); + + if (Common.indexingEnabled()) { await blocksRepository.$saveBlockInDatabase(blockExtended); this.updateTimerProgress(timer, `saved ${this.currentBlockHeight} to database`); - if (!fastForwarded) { - let lastestPriceId; - try { - lastestPriceId = await PricesRepository.$getLatestPriceId(); - this.updateTimerProgress(timer, `got latest price id ${this.currentBlockHeight}`); - } catch (e) { - logger.debug('failed to fetch latest price id from db: ' + (e instanceof Error ? e.message : e)); - } - if (priceUpdater.historyInserted === true && lastestPriceId !== null) { - await blocksRepository.$saveBlockPrices([{ - height: blockExtended.height, - priceId: lastestPriceId, - }]); - this.updateTimerProgress(timer, `saved prices for ${this.currentBlockHeight}`); - } else { - logger.debug(`Cannot save block price for ${blockExtended.height} because the price updater hasnt completed yet. Trying again in 10 seconds.`, logger.tags.mining); - indexer.scheduleSingleTask('blocksPrices', 10000); - } + await AccelerationRepository.$indexAccelerationsForBlock( + blockExtended, + Object.values(accelerations), + cpfpSummary.transactions + ); + this.updateTimerProgress(timer, `indexed accelerations for ${this.currentBlockHeight}`); - // Save blocks summary for visualization if it's enabled - if (Common.blocksSummariesIndexingEnabled() === true) { - await this.$getStrippedBlockTransactions(blockExtended.id, true, false, cpfpSummary, blockExtended.height); - this.updateTimerProgress(timer, `saved block summary for ${this.currentBlockHeight}`); - } - if (config.MEMPOOL.CPFP_INDEXING) { - this.$saveCpfp(blockExtended.id, this.currentBlockHeight, cpfpSummary); - this.updateTimerProgress(timer, `saved cpfp for ${this.currentBlockHeight}`); - } + if (!fastForwarded) { + await this.$saveBlockData(processingResult, timer); } } - // start async callbacks - this.updateTimerProgress(timer, `starting async callbacks for ${this.currentBlockHeight}`); - const callbackPromises = this.newAsyncBlockCallbacks.map((cb) => cb(blockExtended, txIds, cpfpSummary.transactions)); - if (block.height % 2016 === 0) { if (Common.indexingEnabled()) { let adjustment; @@ -1082,11 +1420,6 @@ class Blocks { await chainTips.updateOrphanedBlocks(); } - // wait for pending async callbacks to finish - this.updateTimerProgress(timer, `waiting for async callbacks to complete for ${this.currentBlockHeight}`); - await Promise.all(callbackPromises); - this.updateTimerProgress(timer, `async callbacks completed for ${this.currentBlockHeight}`); - this.blocks.push(blockExtended); if (this.blocks.length > config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4) { this.blocks = this.blocks.slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4); @@ -1103,14 +1436,14 @@ class Blocks { this.newBlockCallbacks.forEach((cb) => cb(blockExtended, txIds, transactions)); } if (config.MEMPOOL.CACHE_ENABLED && !memPool.hasPriority() && (block.height % config.MEMPOOL.DISK_CACHE_BLOCK_INTERVAL === 0)) { - diskCache.$saveCacheToDisk(); + void diskCache.$saveCacheToDisk(); } // Update Redis cache if (config.REDIS.ENABLED) { await redisCache.$updateBlocks(this.blocks); await redisCache.$updateBlockSummaries(this.blockSummaries); - await redisCache.$removeTransactions(txIds); + await redisCache.$removeTransactions(); await rbfCache.updateCache(); } @@ -1146,6 +1479,7 @@ class Blocks { } } + /** @asyncUnsafe */ private async updateQuarterEpochBlockTime(): Promise { if (this.currentBlockHeight >= 503) { try { @@ -1159,6 +1493,10 @@ class Blocks { } } + /** + * Index a block if it's missing from the database. Returns the block after indexing + * @asyncUnsafe + */ public async $indexBlockByHeight(height: number, skipDb = false): Promise { if (Common.indexingEnabled() && !skipDb) { const dbBlock = await blocksRepository.$getBlockByHeight(height); @@ -1171,6 +1509,7 @@ class Blocks { return this.$indexBlock(hash); } + /** @asyncUnsafe */ private async $handleReorgs(blockExtended: BlockExtended, timer: any): Promise { let forkTail = blockExtended; let currentlyIndexed = await blocksRepository.$getBlockByHeight(forkTail.height - 1); @@ -1222,6 +1561,7 @@ class Blocks { await DifficultyAdjustmentsRepository.$deleteAdjustementsFromHeight(forkTail.height); await cpfpRepository.$deleteClustersFrom(forkTail.height); await AccelerationRepository.$deleteAccelerationsFrom(forkTail.height); + this.flagValuesDeleteQueue.push(forkTail.height); chainTips.clearOrphanCacheAboveHeight(forkTail.height); this.updateTimerProgress(timer, `deleted stale block data`); @@ -1242,6 +1582,8 @@ class Blocks { /** * Index a block if it's missing from the database. Returns the block after indexing + * + * @asyncUnsafe */ public async $indexBlock(hash: string, block?: IEsploraApi.Block, skipDb = false): Promise { if (Common.indexingEnabled() && !skipDb) { @@ -1271,6 +1613,7 @@ class Blocks { /** * Get one block by its hash + * @asyncUnsafe */ public async $getBlock(hash: string, skipMemoryCache: boolean = false): Promise { // Check the memory cache @@ -1282,7 +1625,7 @@ class Blocks { } // Not Bitcoin network, return the block as it from the bitcoin backend - if (['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK) === false) { + if (['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK) === false) { return await bitcoinCoreApi.$getBlock(hash); } @@ -1290,6 +1633,7 @@ class Blocks { return await this.$indexBlock(hash); } + /** @asyncUnsafe */ public async $getStrippedBlockTransactions(hash: string, skipMemoryCache = false, skipDBLookup = false, cpfpSummary?: CpfpSummary, blockHeight?: number): Promise { @@ -1358,6 +1702,7 @@ class Blocks { return summary.transactions; } + /** @asyncUnsafe */ public async $getSingleTxFromSummary(hash: string, txid: string): Promise { const txs = await this.$getStrippedBlockTransactions(hash); return txs.find(tx => tx.txid === txid) || null; @@ -1365,15 +1710,16 @@ class Blocks { /** * Get 15 blocks - * + * * Internally this function uses two methods to get the blocks, and * the method is automatically selected: * - Using previous block hash links * - Using block height - * - * @param fromHeight - * @param limit - * @returns + * + * @param fromHeight + * @param limit + * @returns + * @asyncUnsafe */ public async $getBlocks(fromHeight?: number, limit: number = 15): Promise { let currentHeight = fromHeight !== undefined ? fromHeight : this.currentBlockHeight; @@ -1405,9 +1751,10 @@ class Blocks { /** * Used for bulk block data query - * - * @param fromHeight - * @param toHeight + * + * @param fromHeight + * @param toHeight + * @asyncUnsafe */ public async $getBlocksBetweenHeight(fromHeight: number, toHeight: number): Promise { if (!Common.indexingEnabled()) { @@ -1525,7 +1872,7 @@ class Blocks { } public async $getBlockAuditSummary(hash: string): Promise { - if (['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK) && Common.auditIndexingEnabled()) { + if (['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK) && Common.auditIndexingEnabled()) { return BlocksAuditsRepository.$getBlockAudit(hash); } else { return null; @@ -1533,7 +1880,7 @@ class Blocks { } public async $getBlockTxAuditSummary(hash: string, txid: string): Promise { - if (['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK) && Common.auditIndexingEnabled()) { + if (['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK) && Common.auditIndexingEnabled()) { return BlocksAuditsRepository.$getBlockTxAudit(hash, txid); } else { return null; @@ -1556,6 +1903,7 @@ class Blocks { return this.currentBlockHeight; } +/** @asyncUnsafe */ public async $indexCPFP(hash: string, height: number, txs?: MempoolTransactionExtended[], stale?: boolean): Promise { let transactions = txs; if (!transactions) { @@ -1572,22 +1920,23 @@ class Blocks { } if (transactions?.length != null) { - const summary = calculateFastBlockCpfp(height, transactions); + const { cpfpSummary } = await detectTemplateAlgorithm(height, transactions, [], true); - if (!stale) { - await this.$saveCpfp(hash, height, summary); + if (!stale && Common.cpfpIndexingEnabled() === true) { + await this.$saveCpfp(hash, height, cpfpSummary); } - const effectiveFeeStats = Common.calcEffectiveFeeStatistics(summary.transactions); + const effectiveFeeStats = Common.calcEffectiveFeeStatistics(cpfpSummary.transactions); await blocksRepository.$saveEffectiveFeeStats(hash, effectiveFeeStats); - return summary; + return cpfpSummary; } else { logger.err(`Cannot index CPFP for block ${height} - missing transaction data`); return null; } } + /** @asyncSafe */ public async $saveCpfp(hash: string, height: number, cpfpSummary: CpfpSummary): Promise { try { const result = await cpfpRepository.$batchSaveClusters(cpfpSummary.clusters); @@ -1628,6 +1977,30 @@ class Blocks { return null; } } + + public getOldestCoreLogTimestamp(forceRefresh = false): number | null { + if (!forceRefresh && this.oldestCoreLogTimestamp !== undefined) { + return this.oldestCoreLogTimestamp; + } + const debugLogPath = config.CORE_RPC.DEBUG_LOG_PATH; + if (!debugLogPath) { + this.oldestCoreLogTimestamp = null; + return null; + } + try { + this.oldestCoreLogTimestamp = getOldestLogTimestampFromLogs(debugLogPath); + if (this.oldestCoreLogTimestamp !== null) { + logger.info(`Core debug log entries date back to ${new Date(this.oldestCoreLogTimestamp * 1000).toISOString()}`); + } else { + logger.err(`Could not find oldest timestamp in Core debug log file at ${debugLogPath}`); + } + return this.oldestCoreLogTimestamp; + } catch (e) { + this.oldestCoreLogTimestamp = null; + logger.err(`Could not read Core debug log file at ${debugLogPath}. Reason: ${e instanceof Error ? e.message : e}`); + return null; + } + } } export default new Blocks(); diff --git a/backend/src/api/chain-tips.ts b/backend/src/api/chain-tips.ts index b42a83c7e..0d7b8713e 100644 --- a/backend/src/api/chain-tips.ts +++ b/backend/src/api/chain-tips.ts @@ -2,6 +2,7 @@ import config from '../config'; import logger from '../logger'; import { BlockExtended } from '../mempool.interfaces'; import BlocksSummariesRepository from '../repositories/BlocksSummariesRepository'; +import BlocksRepository from '../repositories/BlocksRepository'; import bitcoinApi, { bitcoinCoreApi } from './bitcoin/bitcoin-api-factory'; import bitcoinClient from './bitcoin/bitcoin-client'; import { IEsploraApi } from './bitcoin/esplora-api.interface'; @@ -23,25 +24,29 @@ export interface StaleTip extends ChainTip { export interface OrphanedBlock { height: number; hash: string; + branchlen: number; status: 'valid-fork' | 'valid-headers' | 'headers-only'; prevhash: string; } class ChainTips { private chainTips: ChainTip[] = []; - private staleTips: Record = {}; + private validChainTips: ChainTip[] = []; // 'valid-fork' and 'valid-headers' only, in descending height order + private staleBlocks: Record = {}; private orphanedBlocks: { [hash: string]: OrphanedBlock } = {}; private blockCache: { [hash: string]: OrphanedBlock } = {}; private orphansByHeight: { [height: number]: OrphanedBlock[] } = {}; private indexingOrphanedBlocks = false; private indexingQueue: { blockhash?: string, block?: IEsploraApi.Block, tip: OrphanedBlock }[] = []; - private staleTipsCacheSize = 50; + private staleBlocksCacheSize = 50; private maxIndexingQueueSize = 100; + /** @asyncSafe */ public async updateOrphanedBlocks(): Promise { try { this.chainTips = await bitcoinClient.getChainTips(); + this.validChainTips = this.chainTips.filter(tip => tip.status === 'valid-fork' || tip.status === 'valid-headers').sort((a, b) => b.height - a.height); const activeTipHeight = this.chainTips.find(tip => tip.status === 'active')?.height || (await bitcoinApi.$getBlockHeightTip()); let minIndexHeight = 0; @@ -68,6 +73,7 @@ class ChainTips { orphan = { height: block.height, hash: block.id, + branchlen: chain.branchlen, status: chain.status, prevhash: block.previousblockhash, }; @@ -118,13 +124,7 @@ class ChainTips { this.orphansByHeight[orphan.height].push(orphan); } - const heightsToKeep = new Set(this.chainTips.filter(tip => tip.status !== 'active').map(tip => tip.height)); - const heightsToRemove: number[] = Object.keys(this.staleTips).map(Number).filter(height => !heightsToKeep.has(height)); - for (const height of heightsToRemove) { - delete this.staleTips[height]; - } - - this.trimStaleTipsCache(); + this.trimStaleBlocksCache(); // index new orphaned blocks in the background void this.$indexOrphanedBlocks(); @@ -135,6 +135,7 @@ class ChainTips { } } + /** @asyncSafe */ private async $indexOrphanedBlocks(): Promise { if (this.indexingOrphanedBlocks) { return; @@ -146,16 +147,16 @@ class ChainTips { if (!block && !blockhash) { continue; } - if (blockhash && !block) { - block = await bitcoinCoreApi.$getBlock(blockhash); - } - if (!block) { - continue; - } try { + if (blockhash && !block) { + block = await bitcoinCoreApi.$getBlock(blockhash); + } + if (!block) { + continue; + } let staleBlock: BlockExtended | undefined; const alreadyIndexed = await BlocksSummariesRepository.$isSummaryIndexed(block.id); - const needToCache = Object.keys(this.staleTips).length < this.staleTipsCacheSize || block.height > Object.keys(this.staleTips).map(Number).sort((a, b) => b - a)[this.staleTipsCacheSize - 1]; + const needToCache = this.shouldCacheStaleBlock(block.id, block.height); if (!alreadyIndexed) { staleBlock = await blocks.$indexBlock(block.id, block, true); await blocks.$indexBlockSummary(block.id, block.height, true); @@ -166,30 +167,53 @@ class ChainTips { } if (staleBlock && needToCache) { - const canonicalBlock = await blocks.$indexBlockByHeight(staleBlock.height); - this.staleTips[staleBlock.height] = { - height: staleBlock.height, - hash: staleBlock.id, - branchlen: tip.height - staleBlock.height, - status: tip.status, - stale: staleBlock, - canonical: canonicalBlock, - }; - this.trimStaleTipsCache(); + // ensure the canonical block is correctly indexed + await blocks.$indexBlockByHeight(staleBlock.height); + this.cacheStaleBlock(staleBlock); } } catch (e) { - logger.err(`Failed to index orphaned block ${block.id} at height ${block.height}. Reason: ${e instanceof Error ? e.message : e}`); + logger.err(`Failed to index orphaned block ${block?.id} at height ${block?.height}. Reason: ${e instanceof Error ? e.message : e}`); } } this.indexingOrphanedBlocks = false; } - private trimStaleTipsCache(): void { - const staleTipHeights = Object.keys(this.staleTips).map(Number).sort((a, b) => b - a); - if (staleTipHeights.length > this.staleTipsCacheSize) { - const heightsToDiscard = staleTipHeights.slice(this.staleTipsCacheSize); - for (const height of heightsToDiscard) { - delete this.staleTips[height]; + private shouldCacheStaleBlock(hash: string, height: number): boolean { + // already cached + if (this.staleBlocks[hash]) { + return false; + } + + // cache is not full + const cachedBlocks = Object.values(this.staleBlocks); + if (cachedBlocks.length < this.staleBlocksCacheSize) { + return true; + } + + // otherwise cache if this block is newer than the oldest in the cache + const oldestCachedHeight = cachedBlocks.reduce((min, block) => Math.min(min, block.height), Infinity); + return height >= oldestCachedHeight; + } + + private cacheStaleBlock(block: BlockExtended): void { + this.staleBlocks[block.id] = block; + this.trimStaleBlocksCache(); + } + + // evict the oldest stale blocks until the cache is within the size limit + private trimStaleBlocksCache(): void { + // sort by height + const cachedBlocks = Object.values(this.staleBlocks).sort((a, b) => { + if (b.height !== a.height) { + return b.height - a.height; + } + // tie-break by hash + return a.id.localeCompare(b.id); + }); + // delete everything beyond the size limit + if (cachedBlocks.length > this.staleBlocksCacheSize) { + for (const block of cachedBlocks.slice(this.staleBlocksCacheSize)) { + delete this.staleBlocks[block.id]; } } } @@ -206,8 +230,51 @@ class ChainTips { return this.chainTips; } - public getStaleTips(): StaleTip[] { - return Object.values(this.staleTips).sort((a, b) => b.height - a.height); + /** + * get paginated stale chain tips + * @param fromHeight - start height (exclusive) + * @param count - requested page size (target, but not strictly enforced) + * + * @asyncSafe + */ + public async $getStaleTipsPage(fromHeight: number | undefined, count: number): Promise { + const start = fromHeight === undefined ? 0 : this.validChainTips.findIndex(tip => tip.height < fromHeight); + // no tips beyond the requested height, we can return early + if (start === -1) { + return []; + } + + // fill the response array with hydrated tip data + const tips: StaleTip[] = []; + let lastHeight; + for (let index = start; index < this.validChainTips.length; index++) { + const staleTip = this.validChainTips[index]; + // stretch the page to include any remaining blocks at the last included height to avoid pagination gaps with a height-based cursor + if (tips.length >= count) { + if (staleTip.height !== lastHeight) { + break; + } + } + // fetch blocks from caches if available, or DB otherwise + const canonical = blocks.getBlocks().find(block => block.height === staleTip.height) || await BlocksRepository.$getBlockByHeight(staleTip.height); + let stale: BlockExtended | null | undefined = this.staleBlocks[staleTip.hash]; + if (!stale) { + stale = await BlocksRepository.$getBlockByHash(staleTip.hash); + } + // skip tips with missing block data + if (!canonical || !stale) { + continue; + } + + tips.push({ + ...staleTip, + stale, + canonical, + }); + lastHeight = staleTip.height; + } + + return tips; } clearOrphanCacheAboveHeight(height: number): void { @@ -232,4 +299,4 @@ class ChainTips { } } -export default new ChainTips(); \ No newline at end of file +export default new ChainTips(); diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts index 0f14e0c92..6786bf1c8 100644 --- a/backend/src/api/common.ts +++ b/backend/src/api/common.ts @@ -241,7 +241,7 @@ export class Common { return true; } // scriptsig-not-pushonly - if (vin.scriptsig_asm) { + if (vin.scriptsig_asm?.length) { for (const op of vin.scriptsig_asm.split(' ')) { if (opcodes[op] && opcodes[op] > opcodes['OP_16']) { return true; @@ -376,6 +376,7 @@ export class Common { 'testnet4': 42_000, 'testnet': 2_900_000, 'signet': 211_000, + 'regtest': 0, '': 863_500, }; static isNonStandardVersion(tx: TransactionExtended, height?: number): boolean { @@ -399,6 +400,7 @@ export class Common { 'testnet4': 42_000, 'testnet': 2_900_000, 'signet': 211_000, + 'regtest': 0, '': 863_500, }; static isNonStandardAnchor(vin: IEsploraApi.Vin, height?: number): boolean { @@ -419,6 +421,7 @@ export class Common { 'testnet4': 90_500, 'testnet': 4_550_000, 'signet': 260_000, + 'regtest': 0, '': 905_000, }; static isStandardEphemeralDust(tx: TransactionExtended, height?: number): boolean { @@ -439,6 +442,7 @@ export class Common { 'testnet4': 108_000, 'testnet': 4_750_000, 'signet': 276_500, + 'regtest': 0, '': 921_000, }; static MAX_DATACARRIER_BYTES = 83; @@ -461,6 +465,7 @@ export class Common { 'testnet4': 108_000, 'testnet': 4_750_000, 'signet': 276_500, + 'regtest': 0, '': 921_000, }; static isNonStandardLegacySigops(tx: TransactionExtended, height?: number): boolean { @@ -508,7 +513,7 @@ export class Common { } static setLegacySighashFlags(flags: bigint, scriptsig_asm: string): bigint { - for (const item of scriptsig_asm.split(' ')) { + for (const item of scriptsig_asm?.split(' ') ?? []) { // skip op_codes if (item.startsWith('OP_')) { continue; @@ -744,7 +749,15 @@ export class Common { // fast but bad heuristic to detect possible coinjoins // (at least 5 inputs and 5 outputs, less than half of which are unique amounts, with no address reuse) const addressReuse = Object.keys(reusedOutputAddresses).reduce((acc, key) => Math.max(acc, (reusedInputAddresses[key] || 0) + (reusedOutputAddresses[key] || 0)), 0) > 1; - if (!addressReuse && tx.vin.length >= 5 && tx.vout.length >= 5 && (Object.keys(inValues).length + Object.keys(outValues).length) <= (tx.vin.length + tx.vout.length) / 2 ) { + const tokenRelated = (flags & (TransactionFlags.inscription | TransactionFlags.op_return)) !== 0n; + if (!addressReuse && + tx.vin.length >= 5 && + tx.vout.length >= 5 && + (Object.keys(inValues).length + Object.keys(outValues).length) <= (tx.vin.length + tx.vout.length) / 2 && + !tokenRelated && + tx.vin.length / tx.vout.length < 5 && + tx.vin.length / tx.vout.length > 0.2 + ) { flags |= TransactionFlags.coinjoin; } // more than 5:1 input:output ratio @@ -797,6 +810,7 @@ export class Common { return txs.map(Common.stripTransaction); } + /** @asyncSafe */ static sleep$(ms: number): Promise { return new Promise((resolve) => { setTimeout(() => { @@ -854,7 +868,7 @@ export class Common { static indexingEnabled(): boolean { return ( - ['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK) && + ['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK) && config.DATABASE.ENABLED === true && config.MEMPOOL.INDEXING_BLOCKS_AMOUNT !== 0 ); @@ -911,7 +925,7 @@ export class Common { if (id.indexOf('/') !== -1) { id = id.slice(0, -2); } - + if (id.indexOf('x') !== -1) { // Already a short id return id; } @@ -933,6 +947,13 @@ export class Common { } static findSocketNetwork(addr: string): {network: string | null, url: string} { + if (!addr?.length) { + return { + network: null, + url: '' + }; + } + let network: string | null = null; let url: string = addr; @@ -940,7 +961,7 @@ export class Common { url = addr.split('://')[1]; } - if (!url) { + if (!url?.length) { return { network: null, url: addr, @@ -966,7 +987,15 @@ export class Common { }; } } else if (addr.indexOf('ipv6') !== -1 || (config.LIGHTNING.BACKEND === 'lnd' && url.indexOf(']:'))) { - url = url.split('[')[1].split(']')[0]; + const parts = url.split('['); + if (parts.length < 2) { + return { + network: null, + url: addr, + }; + } else { + url = parts[1].split(']')[0]; + } const ipv = isIP(url); if (ipv === 6) { const parts = addr.split(':'); @@ -1009,8 +1038,18 @@ export class Common { } static calcEffectiveFeeStatistics(transactions: { weight: number, fee?: number, effectiveFeePerVsize?: number, txid: string, acceleration?: boolean }[]): EffectiveFeeStats { - const sortedTxs = transactions.map(tx => { return { txid: tx.txid, weight: tx.weight, rate: tx.effectiveFeePerVsize || ((tx.fee || 0) / (tx.weight / 4)) }; }).sort((a, b) => a.rate - b.rate); - const totalWeight = transactions.reduce((acc, tx) => acc + tx.weight, 0); + // return early with safe default values + if (transactions.length <= 1) { + return { + medianFee: 0, + feeRange: [0, 0, 0, 0, 0, 0, 0], + }; + } + // assume the first transaction is a coinbase if the fee is falsy (0 or undefined) + const nonCoinbaseTransactions = transactions[0].fee ? transactions : transactions.slice(1); + + const sortedTxs = nonCoinbaseTransactions.map(tx => { return { txid: tx.txid, weight: tx.weight, rate: tx.effectiveFeePerVsize || ((tx.fee || 0) / (tx.weight / 4)) }; }).sort((a, b) => a.rate - b.rate); + const totalWeight = nonCoinbaseTransactions.reduce((acc, tx) => acc + tx.weight, 0); // include any unused space let weightCount = config.MEMPOOL.BLOCK_WEIGHT_UNITS - totalWeight; @@ -1039,7 +1078,7 @@ export class Common { // b) the minimum effective fee rate in the last 2% of transactions (in block order) const minFee = Math.min( Common.getNthPercentile(1, sortedTxs).rate, - transactions.slice(-transactions.length / 50).reduce((min, tx) => { return Math.min(min, tx.effectiveFeePerVsize || ((tx.fee || 0) / (tx.weight / 4))); }, Infinity) + nonCoinbaseTransactions.slice(Math.ceil(nonCoinbaseTransactions.length * 49 / 50)).reduce((min, tx) => { return Math.min(min, tx.effectiveFeePerVsize || ((tx.fee || 0) / (tx.weight / 4))); }, Infinity) ); // maximum effective fee heuristic: @@ -1048,7 +1087,7 @@ export class Common { // b) the maximum effective fee rate in the first 2% of transactions (in block order) const maxFee = Math.max( Common.getNthPercentile(99, sortedTxs).rate, - transactions.slice(0, transactions.length / 50).reduce((max, tx) => { return Math.max(max, tx.effectiveFeePerVsize || ((tx.fee || 0) / (tx.weight / 4))); }, 0) + nonCoinbaseTransactions.slice(0, nonCoinbaseTransactions.length / 50).reduce((max, tx) => { return Math.max(max, tx.effectiveFeePerVsize || ((tx.fee || 0) / (tx.weight / 4))); }, 0) ); return { @@ -1062,11 +1101,14 @@ export class Common { } static getNthPercentile(n: number, sortedDistribution: any[]): any { + if (sortedDistribution.length === 0) { + return { rate: 0 }; + } return sortedDistribution[Math.floor((sortedDistribution.length - 1) * (n / 100))]; } static getTransactionFromRequest(req: Request, form: boolean): string { - let rawTx: any = typeof req.body === 'object' && form + const rawTx: any = typeof req.body === 'object' && form ? Object.values(req.body)[0] as any : req.body; if (typeof rawTx !== 'string') { @@ -1167,7 +1209,7 @@ export class Common { } } } - }) + }); } // Pass through the input string untouched @@ -1205,14 +1247,14 @@ export class Common { /** * Class to calculate average fee rates of a list of transactions * at certain weight percentiles, in a single pass - * + * * init with: * maxWeight - the total weight to measure percentiles relative to (e.g. 4MW for a single block) * percentileBandWidth - how many weight units to average over for each percentile (as a % of maxWeight) * percentiles - an array of weight percentiles to compute, in % - * + * * then call .processNext(tx) for each transaction, in descending order - * + * * retrieve the final results with .getFeeStats() */ export class OnlineFeeStatsCalculator { diff --git a/backend/src/api/cpfp.ts b/backend/src/api/cpfp.ts index 953664fcc..30232c90b 100644 --- a/backend/src/api/cpfp.ts +++ b/backend/src/api/cpfp.ts @@ -1,20 +1,30 @@ -import { Ancestor, CpfpCluster, CpfpInfo, CpfpSummary, MempoolTransactionExtended, TransactionExtended } from '../mempool.interfaces'; +import { Ancestor, CpfpCluster, CpfpInfo, MempoolTransactionExtended, TemplateAlgorithm, TransactionExtended } from '../mempool.interfaces'; import { GraphTx, convertToGraphTx, expandRelativesGraph, initializeRelatives, makeBlockTemplate, mempoolComparator, removeAncestors, setAncestorScores } from './mini-miner'; import memPool from './mempool'; import { Acceleration } from './acceleration/acceleration'; +import { ClusterMempool } from '../cluster-mempool/cluster-mempool'; const CPFP_UPDATE_INTERVAL = 60_000; // update CPFP info at most once per 60s per transaction const MAX_CLUSTER_ITERATIONS = 100; -export function calculateFastBlockCpfp(height: number, transactions: MempoolTransactionExtended[], saveRelatives: boolean = false): CpfpSummary { +type TransactionCpfpData = Partial; +export interface BlockCpfpData { + txs: Record, + clusters: CpfpCluster[]; + version: number; +} + +export function calculateFastBlockCpfp(height: number, transactions: MempoolTransactionExtended[], saveRelatives: boolean = false): BlockCpfpData { const clusters: CpfpCluster[] = []; // list of all cpfp clusters in this block const clusterMap: { [txid: string]: CpfpCluster } = {}; // map transactions to their cpfp cluster let clusterTxs: TransactionExtended[] = []; // working list of elements of the current cluster let ancestors: { [txid: string]: boolean } = {}; // working set of ancestors of the current cluster root const txMap: { [txid: string]: TransactionExtended } = {}; + const cpfpData: Record = {}; // initialize the txMap for (const tx of transactions) { txMap[tx.txid] = tx; + cpfpData[tx.txid] = {}; } // reverse pass to identify CPFP clusters for (let i = transactions.length - 1; i >= 0; i--) { @@ -38,7 +48,9 @@ export function calculateFastBlockCpfp(height: number, transactions: MempoolTran clusters.push(cluster); } clusterTxs.forEach(tx => { - txMap[tx.txid].effectiveFeePerVsize = effectiveFeePerVsize; + cpfpData[tx.txid] = { + effectiveFeePerVsize + }; if (cluster) { clusterMap[tx.txid] = cluster; } @@ -54,17 +66,19 @@ export function calculateFastBlockCpfp(height: number, transactions: MempoolTran } // forward pass to enforce ancestor rate caps for (const tx of transactions) { - let minAncestorRate = tx.effectiveFeePerVsize; + const txRate = cpfpData[tx.txid]?.effectiveFeePerVsize ?? tx.effectiveFeePerVsize; + let minAncestorRate = txRate; for (const vin of tx.vin) { - if (txMap[vin.txid]?.effectiveFeePerVsize) { - minAncestorRate = Math.min(minAncestorRate, txMap[vin.txid].effectiveFeePerVsize); + const vinRate = cpfpData[vin.txid]?.effectiveFeePerVsize ?? txMap[vin.txid]?.effectiveFeePerVsize; + if (vinRate) { + minAncestorRate = Math.min(minAncestorRate, vinRate); } } // check rounded values to skip cases with almost identical fees const roundedMinAncestorRate = Math.ceil(minAncestorRate); - const roundedEffectiveFeeRate = Math.floor(tx.effectiveFeePerVsize); + const roundedEffectiveFeeRate = Math.floor(txRate); if (roundedMinAncestorRate < roundedEffectiveFeeRate) { - tx.effectiveFeePerVsize = minAncestorRate; + cpfpData[tx.txid].effectiveFeePerVsize = minAncestorRate; if (!clusterMap[tx.txid]) { // add a single-tx cluster to record the dependent rate const cluster = { @@ -84,23 +98,25 @@ export function calculateFastBlockCpfp(height: number, transactions: MempoolTran if (saveRelatives) { for (const cluster of clusters) { cluster.txs.forEach((member, index) => { - txMap[member.txid].descendants = cluster.txs.slice(0, index).reverse(); - txMap[member.txid].ancestors = cluster.txs.slice(index + 1).reverse(); - txMap[member.txid].effectiveFeePerVsize = cluster.effectiveFeePerVsize; + cpfpData[member.txid].descendants = cluster.txs.slice(0, index).reverse(); + cpfpData[member.txid].ancestors = cluster.txs.slice(index + 1).reverse(); + cpfpData[member.txid].effectiveFeePerVsize = cluster.effectiveFeePerVsize; }); } } return { - transactions, + txs: cpfpData, clusters, version: 1, }; } -export function calculateGoodBlockCpfp(height: number, transactions: MempoolTransactionExtended[], accelerations: Acceleration[]): CpfpSummary { +export function calculateGoodBlockCpfp(height: number, transactions: MempoolTransactionExtended[], accelerations: Acceleration[]): BlockCpfpData { const txMap: { [txid: string]: MempoolTransactionExtended } = {}; + const cpfpData: Record = {}; for (const tx of transactions) { txMap[tx.txid] = tx; + cpfpData[tx.txid] = {}; } const template = makeBlockTemplate(transactions, accelerations, 1, Infinity, Infinity); const clusters = new Map(); @@ -110,15 +126,15 @@ export function calculateGoodBlockCpfp(height: number, transactions: MempoolTran if (cluster.length > 1 && root && !clusters.has(root)) { clusters.set(root, cluster); } - txMap[tx.txid].effectiveFeePerVsize = tx.effectiveFeePerVsize; + cpfpData[tx.txid].effectiveFeePerVsize = tx.effectiveFeePerVsize; } const clusterArray: CpfpCluster[] = []; for (const cluster of clusters.values()) { for (const txid of cluster) { - const mempoolTx = txMap[txid]; - if (mempoolTx) { + const mempoolTxCpfpData = cpfpData[txid]; + if (mempoolTxCpfpData) { const ancestors: Ancestor[] = []; const descendants: Ancestor[] = []; let matched = false; @@ -138,10 +154,10 @@ export function calculateGoodBlockCpfp(height: number, transactions: MempoolTran } } }); - if (mempoolTx.ancestors?.length !== ancestors.length || mempoolTx.descendants?.length !== descendants.length) { - mempoolTx.cpfpDirty = true; + if (mempoolTxCpfpData.ancestors?.length !== ancestors.length || mempoolTxCpfpData.descendants?.length !== descendants.length) { + mempoolTxCpfpData.cpfpDirty = true; } - Object.assign(mempoolTx, { ancestors, descendants, bestDescendant: null, cpfpChecked: true }); + Object.assign(mempoolTxCpfpData, { ancestors, descendants, bestDescendant: null, cpfpChecked: true }); } } const root = cluster[cluster.length - 1]; @@ -153,17 +169,75 @@ export function calculateGoodBlockCpfp(height: number, transactions: MempoolTran fee: txMap[txid].fee, weight: (txMap[txid].adjustedVsize * 4) || txMap[txid].weight, })), - effectiveFeePerVsize: txMap[root].effectiveFeePerVsize, + effectiveFeePerVsize: cpfpData[root].effectiveFeePerVsize ?? txMap[root].effectiveFeePerVsize, }); } return { - transactions: transactions.map(tx => txMap[tx.txid]), + txs: cpfpData, clusters: clusterArray, version: 2, }; } +export function calculateClusterMempoolBlockCpfp(height: number, transactions: MempoolTransactionExtended[], accelerations: Acceleration[]): BlockCpfpData { + const txMap: { [txid: string]: MempoolTransactionExtended } = {}; + const cpfpData: Record = {}; + for (const tx of transactions) { + txMap[tx.txid] = tx; + cpfpData[tx.txid] = {}; + } + + const accelMap: { [txid: string]: { feeDelta: number } } = {}; + for (const acc of accelerations) { + accelMap[acc.txid] = { feeDelta: acc.max_bid }; + } + + const cm = new ClusterMempool(txMap, accelMap, false, 25000); + + const seenClusters = new Set(); + const clusters: CpfpCluster[] = []; + + for (const txid in cpfpData) { + const txCpfpData = cm.getCpfpDataForTx(txid); + if (!txCpfpData) { + continue; + } + cpfpData[txid].effectiveFeePerVsize = txCpfpData.effectiveFeePerVsize; + cpfpData[txid].clusterId = txCpfpData.clusterId; + cpfpData[txid].chunkIndex = txCpfpData.chunkIndex; + cpfpData[txid].ancestors = txCpfpData.ancestors; + cpfpData[txid].descendants = txCpfpData.descendants; + if (txCpfpData.clusterId !== undefined && !seenClusters.has(txCpfpData.clusterId)) { + seenClusters.add(txCpfpData.clusterId); + + const clusterData = cm.getCluster(txCpfpData.clusterId); + if (clusterData && clusterData.txs.length > 1) { + let totalFee = 0; + let totalWeight = 0; + for (const t of clusterData.txs) { + totalFee += t.fee; + totalWeight += t.weight; + } + clusters.push({ + root: clusterData.txs[0].txid, + height, + txs: clusterData.txs.map(t => ({ txid: t.txid, weight: t.weight, fee: t.fee })), + effectiveFeePerVsize: totalFee / (totalWeight / 4), + templateAlgorithm: TemplateAlgorithm.clusterMempool, + clusterData, + }); + } + } + } + + return { + txs: cpfpData, + clusters, + version: 3, + }; +} + /** * Takes a mempool transaction and a copy of the current mempool, and calculates the CPFP data for * that transaction (and all others in the same cluster) @@ -236,7 +310,7 @@ export function calculateMempoolTxCpfp(tx: MempoolTransactionExtended, mempool: /** * Given a root transaction and a list of in-mempool ancestors, * Calculate the CPFP cluster - * + * * @param tx * @param ancestors */ diff --git a/backend/src/api/database-migration.ts b/backend/src/api/database-migration.ts index 00955a758..39bd7787f 100644 --- a/backend/src/api/database-migration.ts +++ b/backend/src/api/database-migration.ts @@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository'; import { RowDataPacket } from 'mysql2'; class DatabaseMigration { - private static currentVersion = 104; + private static currentVersion = 112; private queryTimeout = 3600_000; private statisticsAddedIndexed = false; private uniqueLogs: string[] = []; @@ -28,6 +28,7 @@ class DatabaseMigration { /** * Entry point + * @asyncUnsafe */ public async $initializeOrMigrateDatabase(): Promise { logger.debug('MIGRATIONS: Running migrations'); @@ -100,11 +101,12 @@ class DatabaseMigration { /** * Create all missing tables + * @asyncUnsafe */ private async $createMissingTablesAndIndexes(databaseSchemaVersion: number) { await this.$setStatisticsAddedIndexedFlag(databaseSchemaVersion); - const isBitcoin = ['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK); + const isBitcoin = ['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK); await this.$executeQuery(this.getCreateElementsTableQuery(), await this.$checkIfTableExists('elements_pegs')); await this.$executeQuery(this.getCreateStatisticsQuery(), await this.$checkIfTableExists('statistics')); @@ -566,8 +568,8 @@ class DatabaseMigration { await this.$executeQuery('ALTER TABLE `blocks_templates` ADD INDEX `version` (`version`)'); await this.updateToSchemaVersion(67); } - - if (databaseSchemaVersion < 68 && config.MEMPOOL.NETWORK === "liquid") { + + if (databaseSchemaVersion < 68 && config.MEMPOOL.NETWORK === 'liquid') { await this.$executeQuery('TRUNCATE TABLE elements_pegs'); await this.$executeQuery('ALTER TABLE elements_pegs ADD PRIMARY KEY (txid, txindex);'); await this.$executeQuery(`UPDATE state SET number = 0 WHERE name = 'last_elements_block';`); @@ -931,24 +933,24 @@ class DatabaseMigration { // Version 34 await this.$executeQuery('ALTER TABLE `lightning_stats` ADD clearnet_tor_nodes int(11) NOT NULL DEFAULT "0"'); - + // Version 35 await this.$executeQuery('DELETE from `lightning_stats` WHERE added > "2021-09-19"'); await this.$executeQuery('ALTER TABLE `lightning_stats` ADD CONSTRAINT added_unique UNIQUE (added);'); // Version 36 await this.$executeQuery('ALTER TABLE `nodes` ADD status TINYINT NOT NULL DEFAULT "1"'); - + // Version 37 await this.$executeQuery(this.getCreateLNNodesSocketsTableQuery(), await this.$checkIfTableExists('nodes_sockets')); - + // Version 38 await this.$executeQuery(`TRUNCATE lightning_stats`); await this.$executeQuery(`TRUNCATE node_stats`); await this.$executeQuery('ALTER TABLE `lightning_stats` CHANGE `added` `added` timestamp NULL'); await this.$executeQuery('ALTER TABLE `node_stats` CHANGE `added` `added` timestamp NULL'); await this.updateToSchemaVersion(38); - + // Version 39 await this.$executeQuery('ALTER TABLE `nodes` ADD alias_search TEXT NULL DEFAULT NULL AFTER `alias`'); await this.$executeQuery('ALTER TABLE nodes ADD FULLTEXT(alias_search)'); @@ -963,7 +965,7 @@ class DatabaseMigration { // Version 42 await this.$executeQuery('ALTER TABLE `channels` ADD closing_resolved tinyint(1) DEFAULT 0'); - + // Version 43 await this.$executeQuery(this.getCreateLNNodeRecordsTableQuery(), await this.$checkIfTableExists('nodes_records')); @@ -972,7 +974,7 @@ class DatabaseMigration { // Version 45 await this.$executeQuery('ALTER TABLE `blocks_audits` ADD fresh_txs JSON DEFAULT "[]"'); - + // Version 48 await this.$executeQuery('ALTER TABLE `channels` ADD source_checked tinyint(1) DEFAULT 0'); await this.$executeQuery('ALTER TABLE `channels` ADD closing_fee bigint(20) unsigned DEFAULT 0'); @@ -1002,13 +1004,13 @@ class DatabaseMigration { // Version 62 await this.$executeQuery('ALTER TABLE `blocks_audits` ADD expected_fees BIGINT UNSIGNED DEFAULT NULL'); await this.$executeQuery('ALTER TABLE `blocks_audits` ADD expected_weight BIGINT UNSIGNED DEFAULT NULL'); - + // Version 63 await this.$executeQuery('ALTER TABLE `blocks_audits` ADD fullrbf_txs JSON DEFAULT "[]"'); - + // Version 64 await this.$executeQuery('ALTER TABLE `nodes` ADD features text NULL'); - + // Version 65 await this.$executeQuery('ALTER TABLE `blocks_audits` ADD accelerated_txs JSON DEFAULT "[]"'); @@ -1044,8 +1046,8 @@ class DatabaseMigration { ADD INDEX \`closing_reason\` (\`closing_reason\`), ADD INDEX \`closing_resolved\` (\`closing_resolved\`) `); - - // Version 86 + + // Version 86 await this.$executeQuery(` ALTER TABLE \`nodes\` ADD INDEX \`status\` (\`status\`), @@ -1058,20 +1060,20 @@ class DatabaseMigration { // Version 87 await this.$executeQuery('ALTER TABLE `nodes_sockets` ADD INDEX `type` (`type`)'); await this.updateToSchemaVersion(87); - + // Version 88 await this.$executeQuery('ALTER TABLE `lightning_stats` ADD INDEX `added` (`added`)'); - + // Version 89 await this.$executeQuery('ALTER TABLE `geo_names` ADD INDEX `names` (`names`)'); - + // Version 90 await this.$executeQuery('ALTER TABLE `hashrates` ADD INDEX `type` (`type`)'); // Version 91 await this.$executeQuery('ALTER TABLE `blocks_audits` ADD INDEX `time` (`time`)'); } - + if (config.MEMPOOL.NETWORK !== 'liquid') { // Apply all the liquid specific migrations to all other networks // Version 68 @@ -1093,7 +1095,7 @@ class DatabaseMigration { ADD INDEX \`bitcoinaddress\` (\`bitcoinaddress\`), ADD INDEX \`bitcointxid\` (\`bitcointxid\`) `); - + // Version 93 await this.$executeQuery(` ALTER TABLE \`federation_txos\` @@ -1178,6 +1180,86 @@ class DatabaseMigration { await this.$executeQuery('ALTER TABLE `blocks` ADD INDEX `stale` (`stale`)'); await this.updateToSchemaVersion(103); } + + // reindex liquid federation addresses and txos when needed, and add hardcoded federation addresses + // (safe to make this conditional on the network since it doesn't change the database schema) + if (databaseSchemaVersion < 105 && config.MEMPOOL.NETWORK === 'liquid') { + // Hardcoded federation addresses + await this.$executeQuery(`INSERT IGNORE INTO federation_addresses (bitcoinaddress) VALUES ('3G6neksSBMp51kHJ2if8SeDUrzT8iVETWT')`); + await this.$executeQuery(`INSERT IGNORE INTO federation_addresses (bitcoinaddress) VALUES ('bc1qwnevjp8nsq7adu3hxlvdvslrf242q4vuavfg0y929jp2zntp3vgq7cq6z2')`); + + // Rollback only on up to date instances + const [stateRows]: any[] = await DB.query(`SELECT name, number FROM state WHERE name IN ('last_elements_block', 'last_bitcoin_block_audit')`); + const lastElementsBlock = Number(stateRows?.find((row: any) => row.name === 'last_elements_block')?.number ?? 0); + const lastBlockAudit = Number(stateRows?.find((row: any) => row.name === 'last_bitcoin_block_audit')?.number ?? 0); + if (lastElementsBlock > 3686608 && lastBlockAudit > 929700) { + await this.$executeQuery('DELETE FROM elements_pegs WHERE block > 3686608'); + await this.$executeQuery('DELETE FROM federation_txos WHERE blocknumber > 929701'); + await this.$executeQuery(`UPDATE federation_txos SET lastblockupdate = 929700 WHERE unspent = 1;`); + await this.$executeQuery(`UPDATE state SET number = 3686608 WHERE name = 'last_elements_block';`); + await this.$executeQuery(`UPDATE state SET number = 929700 WHERE name = 'last_bitcoin_block_audit';`); + } + await this.updateToSchemaVersion(105); + } + + // another liquid failure, fix bad timelocks on federation txos + // (safe to make this conditional on the network since it doesn't change the database schema) + if (databaseSchemaVersion < 106 && config.MEMPOOL.NETWORK === 'liquid') { + // In a specific setup it's possible that 3G6neksSBMp51kHJ2if8SeDUrzT8iVETWT and bc1qwnevjp8nsq7adu3hxlvdvslrf242q4vuavfg0y929jp2zntp3vgq7cq6z2 + // were set with a timelock of 2016 instead of 4032 + // This rollbacks the tables to before bc1qwnevjp8nsq7adu3hxlvdvslrf242q4vuavfg0y929jp2zntp3vgq7cq6z2 is used, and + // manually fixes the timelock for 3G6neksSBMp51kHJ2if8SeDUrzT8iVETWT + const [stateRows]: any[] = await DB.query(`SELECT name, number FROM state WHERE name IN ('last_elements_block', 'last_bitcoin_block_audit')`); + const lastElementsBlock = Number(stateRows?.find((row: any) => row.name === 'last_elements_block')?.number ?? 0); + const lastBlockAudit = Number(stateRows?.find((row: any) => row.name === 'last_bitcoin_block_audit')?.number ?? 0); + if (lastElementsBlock > 3686608 && lastBlockAudit > 929700) { + await this.$executeQuery('DELETE FROM elements_pegs WHERE block > 3686608'); + await this.$executeQuery('DELETE FROM federation_txos WHERE blocknumber > 929701'); + await this.$executeQuery(`UPDATE federation_txos SET lastblockupdate = 929700 WHERE unspent = 1;`); + await this.$executeQuery(`UPDATE federation_txos SET timelock = 4032 WHERE bitcoinaddress = '3G6neksSBMp51kHJ2if8SeDUrzT8iVETWT';`); + await this.$executeQuery(`UPDATE state SET number = 3686608 WHERE name = 'last_elements_block';`); + await this.$executeQuery(`UPDATE state SET number = 929700 WHERE name = 'last_bitcoin_block_audit';`); + } + await this.updateToSchemaVersion(106); + } + + if (databaseSchemaVersion < 107) { + await this.$executeQuery('ALTER TABLE `federation_txos` DROP FOREIGN KEY IF EXISTS `federation_txos_ibfk_1`'); + await this.$executeQuery('DROP TABLE IF EXISTS `federation_addresses`'); + await this.updateToSchemaVersion(107); + } + + if (databaseSchemaVersion < 108) { + await this.$executeQuery(this.getCreateFederationPegScriptsTableQuery(), await this.$checkIfTableExists('federation_peg_scripts'),); + await this.updateToSchemaVersion(108); + } + + // safe to make this conditional on the network since it doesn't change the database schema + if (databaseSchemaVersion < 109 && config.MEMPOOL.NETWORK === 'liquid') { + // hardcode current and past federation peg scripts to avoid reindexing existing instances + await this.$executeQuery(` + INSERT IGNORE INTO federation_peg_scripts (address, fedpegscript, timelock, blocknumber) VALUES + ('3G6neksSBMp51kHJ2if8SeDUrzT8iVETWT', '745c87635b21020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b678172612102675333a4e4b8fb51d9d4e22fa5a8eaced3fdac8a8cbf9be8c030f75712e6af992102896807d54bc55c24981f24a453c60ad3e8993d693732288068a23df3d9f50d4821029e51a5ef5db3137051de8323b001749932f2ff0d34c82e96a2c2461de96ae56c2102a4e1a9638d46923272c266631d94d36bdb03a64ee0e14c7518e49d2f29bc40102102f8a00b269f8c5e59c67d36db3cdc11b11b21f64b4bffb2815e9100d9aa8daf072103079e252e85abffd3c401a69b087e590a9b86f33f574f08129ccbd3521ecf516b2103111cf405b627e22135b3b3733a4a34aa5723fb0f58379a16d32861bf576b0ec2210318f331b3e5d38156da6633b31929c5b220349859cc9ca3d33fb4e68aa08401742103230dae6b4ac93480aeab26d000841298e3b8f6157028e47b0897c1e025165de121035abff4281ff00660f99ab27bb53e6b33689c2cd8dcd364bc3c90ca5aea0d71a62103bd45cddfacf2083b14310ae4a84e25de61e451637346325222747b157446614c2103cc297026b06c71cbfa52089149157b5ff23de027ac5ab781800a578192d175462103d3bde5d63bdb3a6379b461be64dad45eabff42f758543a9645afd42f6d4248282103ed1e8d5109c9ed66f7941bc53cc71137baa76d50d274bda8d5e8ffbd6e61fe9a5f6702c00fb275522103aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79210291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807210386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb5368ae', 4032, 0), + ('3EiAcrzq1cELXScc98KeCswGWZaPGceT1d', '745c87635b21020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b678172612102675333a4e4b8fb51d9d4e22fa5a8eaced3fdac8a8cbf9be8c030f75712e6af992102896807d54bc55c24981f24a453c60ad3e8993d693732288068a23df3d9f50d4821029e51a5ef5db3137051de8323b001749932f2ff0d34c82e96a2c2461de96ae56c2102a4e1a9638d46923272c266631d94d36bdb03a64ee0e14c7518e49d2f29bc40102102f8a00b269f8c5e59c67d36db3cdc11b11b21f64b4bffb2815e9100d9aa8daf072103079e252e85abffd3c401a69b087e590a9b86f33f574f08129ccbd3521ecf516b2103111cf405b627e22135b3b3733a4a34aa5723fb0f58379a16d32861bf576b0ec2210318f331b3e5d38156da6633b31929c5b220349859cc9ca3d33fb4e68aa08401742103230dae6b4ac93480aeab26d000841298e3b8f6157028e47b0897c1e025165de121035abff4281ff00660f99ab27bb53e6b33689c2cd8dcd364bc3c90ca5aea0d71a62103bd45cddfacf2083b14310ae4a84e25de61e451637346325222747b157446614c2103cc297026b06c71cbfa52089149157b5ff23de027ac5ab781800a578192d175462103d3bde5d63bdb3a6379b461be64dad45eabff42f758543a9645afd42f6d4248282103ed1e8d5109c9ed66f7941bc53cc71137baa76d50d274bda8d5e8ffbd6e61fe9a5f6702e007b275522103aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79210291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807210386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb5368ae', 2016, 0), + ('bc1qxvay4an52gcghxq5lavact7r6qe9l4laedsazz8fj2ee2cy47tlqff4aj4', '5b21020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b678172612102675333a4e4b8fb51d9d4e22fa5a8eaced3fdac8a8cbf9be8c030f75712e6af992102896807d54bc55c24981f24a453c60ad3e8993d693732288068a23df3d9f50d4821029e51a5ef5db3137051de8323b001749932f2ff0d34c82e96a2c2461de96ae56c2102a4e1a9638d46923272c266631d94d36bdb03a64ee0e14c7518e49d2f29bc401021031c41fdbcebe17bec8d49816e00ca1b5ac34766b91c9f2ac37d39c63e5e008afb2103079e252e85abffd3c401a69b087e590a9b86f33f574f08129ccbd3521ecf516b2103111cf405b627e22135b3b3733a4a34aa5723fb0f58379a16d32861bf576b0ec2210318f331b3e5d38156da6633b31929c5b220349859cc9ca3d33fb4e68aa08401742103230dae6b4ac93480aeab26d000841298e3b8f6157028e47b0897c1e025165de121035abff4281ff00660f99ab27bb53e6b33689c2cd8dcd364bc3c90ca5aea0d71a62103bd45cddfacf2083b14310ae4a84e25de61e451637346325222747b157446614c2103cc297026b06c71cbfa52089149157b5ff23de027ac5ab781800a578192d175462103d3bde5d63bdb3a6379b461be64dad45eabff42f758543a9645afd42f6d4248282103ed1e8d5109c9ed66f7941bc53cc71137baa76d50d274bda8d5e8ffbd6e61fe9a5fae736402c00fb269522103aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79210291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807210386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb53ae68', 4032, 2197440), + ('bc1qwnevjp8nsq7adu3hxlvdvslrf242q4vuavfg0y929jp2zntp3vgq7cq6z2', '5b21020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b678172612102675333a4e4b8fb51d9d4e22fa5a8eaced3fdac8a8cbf9be8c030f75712e6af9921021fad939f956beae2e6765b450552f1a75236c8b7314ec30474d8f4978b252ab221029e51a5ef5db3137051de8323b001749932f2ff0d34c82e96a2c2461de96ae56c2102a4e1a9638d46923272c266631d94d36bdb03a64ee0e14c7518e49d2f29bc401021031c41fdbcebe17bec8d49816e00ca1b5ac34766b91c9f2ac37d39c63e5e008afb2102e323a079079582cf6bb68583df94bd68c9590e5904062b503ef5476109ad739e2103111cf405b627e22135b3b3733a4a34aa5723fb0f58379a16d32861bf576b0ec2210318f331b3e5d38156da6633b31929c5b220349859cc9ca3d33fb4e68aa08401742103230dae6b4ac93480aeab26d000841298e3b8f6157028e47b0897c1e025165de121035abff4281ff00660f99ab27bb53e6b33689c2cd8dcd364bc3c90ca5aea0d71a62103bd45cddfacf2083b14310ae4a84e25de61e451637346325222747b157446614c2103cc297026b06c71cbfa52089149157b5ff23de027ac5ab781800a578192d175462103d3bde5d63bdb3a6379b461be64dad45eabff42f758543a9645afd42f6d4248282103ed1e8d5109c9ed66f7941bc53cc71137baa76d50d274bda8d5e8ffbd6e61fe9a5fae736402c00fb269522103aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79210291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807210386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb53ae68', 4032, 3729600); + `); + await this.updateToSchemaVersion(109); + } + if (databaseSchemaVersion < 110 && isBitcoin === true) { + await this.$executeQuery('ALTER TABLE `blocks_audits` ADD template_algo TINYINT UNSIGNED NOT NULL DEFAULT 0'); + await this.updateToSchemaVersion(110); + } + + if (databaseSchemaVersion < 111 && isBitcoin === true) { + await this.$executeQuery('ALTER TABLE `compact_cpfp_clusters` ADD template_algo TINYINT UNSIGNED NOT NULL DEFAULT 0'); + await this.updateToSchemaVersion(111); + } + + if (databaseSchemaVersion < 112) { + await this.$executeQuery(this.getCreateFlagsValuesTableQuery(), await this.$checkIfTableExists('flag_values')); + await this.updateToSchemaVersion(112); + } } /** @@ -1224,6 +1306,7 @@ class DatabaseMigration { /** * Check if 'table' exists in the database + * @asyncUnsafe */ private async $checkIfTableExists(table: string): Promise { const query = `SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '${config.DATABASE.DATABASE}' AND TABLE_NAME = '${table}'`; @@ -1233,6 +1316,7 @@ class DatabaseMigration { /** * Get current database version + * @asyncUnsafe */ private async $getSchemaVersionFromDatabase(): Promise { const query = `SELECT number FROM state WHERE name = 'schema_version';`; @@ -1242,6 +1326,7 @@ class DatabaseMigration { /** * Create the `state` table + * @asyncUnsafe */ private async $createMigrationStateTable(): Promise { const query = `CREATE TABLE IF NOT EXISTS state ( @@ -1259,6 +1344,7 @@ class DatabaseMigration { /** * We actually execute the migrations queries here + * @asyncUnsafe */ private async $migrateTableSchemaFromVersion(version: number): Promise { const transactionQueries: string[] = []; @@ -1286,7 +1372,7 @@ class DatabaseMigration { */ private getMigrationQueriesFromVersion(version: number): string[] { const queries: string[] = []; - const isBitcoin = ['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK); + const isBitcoin = ['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK); if (version < 1) { if (config.MEMPOOL.NETWORK !== 'liquid' && config.MEMPOOL.NETWORK !== 'liquidtestnet') { @@ -1325,11 +1411,13 @@ class DatabaseMigration { /** * Save the schema version in the database + * @asyncUnsafe */ private getUpdateToLatestSchemaVersionQuery(): string { return `UPDATE state SET number = ${DatabaseMigration.currentVersion} WHERE name = 'schema_version';`; } + /** @asyncUnsafe */ private async updateToSchemaVersion(version): Promise { await this.$executeQuery(`UPDATE state SET number = ${version} WHERE name = 'schema_version';`); } @@ -1456,11 +1544,21 @@ class DatabaseMigration { pegtxid varchar(65) NOT NULL, pegindex int(11) NOT NULL, pegblocktime int(11) unsigned NOT NULL, - PRIMARY KEY (txid, txindex), + PRIMARY KEY (txid, txindex), FOREIGN KEY (bitcoinaddress) REFERENCES federation_addresses (bitcoinaddress) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;`; } + private getCreateFederationPegScriptsTableQuery(): string { + return `CREATE TABLE IF NOT EXISTS federation_peg_scripts ( + address varchar(100) NOT NULL, + fedpegscript text NOT NULL, + timelock int(11) unsigned NOT NULL, + blocknumber int(11) unsigned NOT NULL, + PRIMARY KEY (address) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8;`; + } + private getCreatePoolsTableQuery(): string { return `CREATE TABLE IF NOT EXISTS pools ( id int(11) NOT NULL AUTO_INCREMENT, @@ -1748,6 +1846,19 @@ class DatabaseMigration { ) ENGINE=InnoDB DEFAULT CHARSET=utf8;`; } + private getCreateFlagsValuesTableQuery(): string { + return `CREATE TABLE IF NOT EXISTS flag_values ( + bucket_size enum('1', '1008', '4032') NOT NULL, + start_height int unsigned NOT NULL, + avg_timestamp timestamp NOT NULL, + flag_value bigint unsigned NOT NULL, + tx_count int unsigned NOT NULL, + vsize_total int unsigned NOT NULL, + PRIMARY KEY (bucket_size, start_height, flag_value) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8`; + } + + /** @asyncUnsafe */ public async $blocksReindexingTruncate(): Promise { logger.warn(`Truncating pools, blocks, hashrates and difficulty_adjustments tables for re-indexing (using '--reindex-blocks'). You can cancel this command within 5 seconds`); await Common.sleep$(5000); diff --git a/backend/src/api/disk-cache.ts b/backend/src/api/disk-cache.ts index c6106429d..92d52b93f 100644 --- a/backend/src/api/disk-cache.ts +++ b/backend/src/api/disk-cache.ts @@ -33,11 +33,12 @@ class DiskCache { return; } process.on('SIGINT', (e) => { - this.$saveCacheToDisk(true); + void this.$saveCacheToDisk(true); process.exit(0); }); } + /** @asyncSafe */ async $saveCacheToDisk(sync: boolean = false): Promise { if (!cluster.isPrimary || !config.MEMPOOL.CACHE_ENABLED) { return; @@ -174,6 +175,7 @@ class DiskCache { } } + /** @asyncSafe */ async $loadMempoolCache(): Promise { if (!config.MEMPOOL.CACHE_ENABLED || !fs.existsSync(DiskCache.FILE_NAME)) { return; diff --git a/backend/src/api/explorer/channels.api.ts b/backend/src/api/explorer/channels.api.ts index 2faf06c33..7d691e469 100644 --- a/backend/src/api/explorer/channels.api.ts +++ b/backend/src/api/explorer/channels.api.ts @@ -53,7 +53,7 @@ class ChannelsApi { GROUP BY nodes_1.public_key, nodes_2.public_key ORDER BY channels.capacity DESC LIMIT 10000 - `; + `; } const [rows]: any = await DB.query(query, params); @@ -241,10 +241,10 @@ class ChannelsApi { let [feeRates2]: any = await DB.query(query); feeRates2 = feeRates2.map(rate => rate.node2_fee_rate); - let feeRates = (feeRates1.concat(feeRates2)).sort((a, b) => a - b); + const feeRates = (feeRates1.concat(feeRates2)).sort((a, b) => a - b); let avgFeeRate = 0; for (const rate of feeRates) { - avgFeeRate += rate; + avgFeeRate += rate; } avgFeeRate /= feeRates.length; const medianFeeRate = feeRates[Math.floor(feeRates.length / 2)]; @@ -257,14 +257,14 @@ class ChannelsApi { let [baseFees2]: any = await DB.query(query); baseFees2 = baseFees2.map(rate => rate.node2_base_fee_mtokens); - let baseFees = (baseFees1.concat(baseFees2)).sort((a, b) => a - b); + const baseFees = (baseFees1.concat(baseFees2)).sort((a, b) => a - b); let avgBaseFee = 0; for (const fee of baseFees) { - avgBaseFee += fee; + avgBaseFee += fee; } avgBaseFee /= baseFees.length; const medianBaseFee = feeRates[Math.floor(baseFees.length / 2)]; - + return { avgCapacity: parseInt(avgCapacity[0].avgCapacity, 10), avgFeeRate: avgFeeRate, @@ -272,7 +272,7 @@ class ChannelsApi { medianCapacity: medianCapacity, medianFeeRate: medianFeeRate, medianBaseFee: medianBaseFee, - } + }; } catch (e) { logger.err(`Cannot calculate channels statistics. Reason: ${e instanceof Error ? e.message : e}`); @@ -298,6 +298,7 @@ class ChannelsApi { } } + /** @asyncSafe */ public async $getChannelByClosingId(transactionId: string): Promise { try { const query = ` @@ -338,6 +339,7 @@ class ChannelsApi { } } + /** @asyncSafe */ public async $updateClosingInfo(channelInfo: { id: string, node1_closing_balance: number, node2_closing_balance: number, closed_by: string | null, closing_fee: number, outputs: ILightningApi.ForensicOutput[]}): Promise { try { const query = ` @@ -363,6 +365,7 @@ class ChannelsApi { } } + /** @asyncSafe */ public async $updateOpeningInfo(channelInfo: { id: string, node1_funding_balance: number, node2_funding_balance: number, funding_ratio: number, single_funded: boolean | void }): Promise { try { const query = ` @@ -456,7 +459,7 @@ class ChannelsApi { allChannels = allChannels.slice(0, 1000); } - const channels: any[] = [] + const channels: any[] = []; for (const row of allChannels) { let channel; if (index >= 0) { @@ -578,8 +581,12 @@ class ChannelsApi { /** * Save or update a channel present in the graph + * @asyncUnsafe */ public async $saveChannel(channel: ILightningApi.Channel, status = 1): Promise { + if (!channel.chan_point?.length) { + return; + } const [ txid, vout ] = channel.chan_point.split(':'); const policy1: Partial = channel.node1_policy || {}; @@ -714,6 +721,7 @@ class ChannelsApi { } } + /** @asyncSafe */ public async $getLatestChannelUpdateForNode(publicKey: string): Promise { try { const query = ` diff --git a/backend/src/api/explorer/channels.routes.ts b/backend/src/api/explorer/channels.routes.ts index 031aeea17..71bc77280 100644 --- a/backend/src/api/explorer/channels.routes.ts +++ b/backend/src/api/explorer/channels.routes.ts @@ -78,17 +78,14 @@ class ChannelsRoutes { private async $getChannelsByTransactionIds(req: Request, res: Response): Promise { try { - if (!Array.isArray(req.query.txId)) { - handleError(req, res, 400, 'Not an array'); + if (!req.query.txId || typeof req.query.txId !== 'object') { + handleError(req, res, 400, 'invalid txId format'); return; } const txIds: string[] = []; - for (const _txId in req.query.txId) { - if (typeof req.query.txId[_txId] === 'string') { - const txid = req.query.txId[_txId].toString(); - if (TXID_REGEX.test(txid)) { - txIds.push(txid); - } + for (const txid of Object.values(req.query.txId)) { + if (typeof txid === 'string' && TXID_REGEX.test(txid)) { + txIds.push(txid); } } const channels = await channelsApi.$getChannelsByTransactionId(txIds); diff --git a/backend/src/api/explorer/nodes.api.ts b/backend/src/api/explorer/nodes.api.ts index 22c854fcc..c7bf86cdf 100644 --- a/backend/src/api/explorer/nodes.api.ts +++ b/backend/src/api/explorer/nodes.api.ts @@ -34,7 +34,7 @@ class NodesApi { `; const [maximums]: any[] = await DB.query(query); - + return { maxLiquidity: maximums[0].maxLiquidity, maxChannels: maximums[0].maxChannels, @@ -78,7 +78,7 @@ class NodesApi { node.city = JSON.parse(node.city); node.country = JSON.parse(node.country); - // Features + // Features node.features = JSON.parse(node.features); node.featuresBits = null; if (node.features) { @@ -87,7 +87,7 @@ class NodesApi { maxBit = Math.max(maxBit, feature.bit); } maxBit = Math.ceil(maxBit / 4) * 4 - 1; - + node.featuresBits = new Array(maxBit + 1).fill(0); for (const feature of node.features) { node.featuresBits[feature.bit] = 1; @@ -143,6 +143,7 @@ class NodesApi { } } + /** @asyncUnsafe */ public async $getActiveChannelsStats(node_public_key: string): Promise { const query = ` SELECT count(short_id) as active_channel_count, sum(capacity) as capacity @@ -394,7 +395,7 @@ class NodesApi { try { const publicKeySearch = search.replace(/[^a-zA-Z0-9]/g, '') + '%'; const aliasSearch = search - .replace(/[-_.]/g, ' ') // Replace all -_. characters with empty space. Eg: "ln.nicehash" becomes "ln nicehash". + .replace(/[-_.]/g, ' ') // Replace all -_. characters with empty space. Eg: "ln.nicehash" becomes "ln nicehash". .replace(/[^a-zA-Z0-9 ]/g, '') // Remove all special characters and keep just A to Z, 0 to 9. .split(' ') .filter(key => key.length) @@ -455,7 +456,7 @@ class NodesApi { } else if (ispList[isp2].ids.includes(channel.isp2ID) === false) { ispList[isp2].ids.push(channel.isp2ID); } - + ispList[isp1].capacity += channel.capacity; ispList[isp1].channels += 1; ispList[isp1].nodes[channel.node1PublicKey] = true; @@ -463,7 +464,7 @@ class NodesApi { ispList[isp2].channels += 1; ispList[isp2].nodes[channel.node2PublicKey] = true; } - + const ispRanking: any[] = []; for (const isp of Object.keys(ispList)) { ispRanking.push([ @@ -494,7 +495,7 @@ class NodesApi { `; const [clearnetCapacity]: any = await DB.query(query); - // Get the total capacity of all channels which have both nodes on Tor + // Get the total capacity of all channels which have both nodes on Tor query = ` SELECT SUM(capacity) as capacity FROM ( @@ -642,11 +643,11 @@ class NodesApi { for (const country of nodesCountPerCountry) { nodesPerCountry.push({ name: JSON.parse(country.names), - iso: country.iso_code, + iso: country.iso_code, count: country.nodesCount, share: Math.floor(country.nodesCount / nodesWithAS[0].total * 10000) / 100, capacity: country.capacity, - }) + }); } return nodesPerCountry; @@ -658,6 +659,7 @@ class NodesApi { /** * Save or update a node present in the graph + * @asyncSafe */ public async $saveNode(node: ILightningApi.Node): Promise { try { @@ -665,7 +667,7 @@ class NodesApi { if ((node.last_update ?? 0) < 1514736061) { // January 1st 2018 node.last_update = null; } - + const uniqueAddr = [...new Set(node.addresses?.map(a => a.addr))]; const formattedSockets = (uniqueAddr.join(',')) ?? ''; @@ -727,6 +729,7 @@ class NodesApi { /** * Set all nodes not in `nodesPubkeys` as inactive (status = 0) + * @asyncSafe */ public async $setNodesInactive(graphNodesPubkeys: string[]): Promise { if (graphNodesPubkeys.length === 0) { diff --git a/backend/src/api/explorer/nodes.routes.ts b/backend/src/api/explorer/nodes.routes.ts index 811292b4b..113e29b9a 100644 --- a/backend/src/api/explorer/nodes.routes.ts +++ b/backend/src/api/explorer/nodes.routes.ts @@ -39,7 +39,7 @@ class NodesRoutes { private async $getNodeGroup(req: Request, res: Response) { try { let nodesList; - let nodes: any[] = []; + const nodes: any[] = []; switch (config.MEMPOOL.NETWORK) { case 'testnet': nodesList = [ @@ -174,7 +174,7 @@ class NodesRoutes { ]; } - for (let pubKey of nodesList) { + for (const pubKey of nodesList) { try { const node = await nodesApi.$getNode(pubKey); if (node) { @@ -354,7 +354,7 @@ class NodesRoutes { return; } - const nodes = await nodesApi.$getNodesPerISP(req.params.isp); + const nodes = await nodesApi.$getNodesPerISP(req.params.isp || ''); res.header('Pragma', 'public'); res.header('Cache-control', 'public'); res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); diff --git a/backend/src/api/fee-api.ts b/backend/src/api/fee-api.ts index 2edb8c0e4..56106c7f5 100644 --- a/backend/src/api/fee-api.ts +++ b/backend/src/api/fee-api.ts @@ -18,6 +18,9 @@ class FeeApi { constructor() { } minimumIncrement = isLiquid ? 0.1 : 1; + minFastestFee = isLiquid ? 0.1 : 1; + minHalfHourFee = isLiquid ? 0.1 : 0.5; + priorityFactor = isLiquid ? 0 : 0.5; public getRecommendedFee(): RecommendedFees { const pBlocks = projectedBlocks.getMempoolBlocks(); @@ -26,17 +29,27 @@ class FeeApi { return this.calculateRecommendedFee(pBlocks, mPool); } - public getPreciseRecommendedFee(minimum: number = 0): RecommendedFees { + public getPreciseRecommendedFee(): RecommendedFees { const pBlocks = projectedBlocks.getMempoolBlocks(); const mPool = mempool.getMempoolInfo(); // minimum non-zero minrelaytxfee / incrementalrelayfee is 1 sat/kvB = 0.001 sat/vB - return this.calculateRecommendedFee(pBlocks, mPool, minimum, 0.001); + const recommendations = this.calculateRecommendedFee(pBlocks, mPool, 0.001); + // enforce floor & offset for highest priority recommendations while <100% hashrate accepts sub-sat fees + recommendations.fastestFee = Math.max(recommendations.fastestFee + this.priorityFactor, this.minFastestFee); + recommendations.halfHourFee = Math.max(recommendations.halfHourFee + (this.priorityFactor / 2), this.minHalfHourFee); + return { + 'fastestFee': Math.round(recommendations.fastestFee * 1000) / 1000, + 'halfHourFee': Math.round(recommendations.halfHourFee * 1000) / 1000, + 'hourFee': Math.round(recommendations.hourFee * 1000) / 1000, + 'economyFee': Math.round(recommendations.economyFee * 1000) / 1000, + 'minimumFee': Math.round(recommendations.minimumFee * 1000) / 1000, + }; } - public calculateRecommendedFee(pBlocks: MempoolBlock[], mPool: IBitcoinApi.MempoolInfo, minimumRecommendation: number = 0, minIncrement: number = this.minimumIncrement): RecommendedFees { + public calculateRecommendedFee(pBlocks: MempoolBlock[], mPool: IBitcoinApi.MempoolInfo, minIncrement: number = this.minimumIncrement): RecommendedFees { const purgeRate = this.roundUpToNearest(mPool.mempoolminfee * 100000, minIncrement); - const minimumFee = Math.max(purgeRate, minimumRecommendation, minIncrement); + const minimumFee = Math.max(purgeRate, minIncrement); if (!pBlocks.length) { return { diff --git a/backend/src/api/fetch-version.ts b/backend/src/api/fetch-version.ts index cb0813c35..7183007a8 100644 --- a/backend/src/api/fetch-version.ts +++ b/backend/src/api/fetch-version.ts @@ -1,5 +1,5 @@ import fs from 'fs'; -import path from "path"; +import path from 'path'; const { spawnSync } = require('child_process'); function getVersion(): string { @@ -29,9 +29,9 @@ function getGitCommit(): string { const versionInfo = { version: getVersion(), gitCommit: getGitCommit() -} +}; fs.writeFileSync( path.join(__dirname, 'version.json'), - JSON.stringify(versionInfo, null, 2) + "\n" + JSON.stringify(versionInfo, null, 2) + '\n' ); diff --git a/backend/src/api/lightning/clightning/clightning-client.ts b/backend/src/api/lightning/clightning/clightning-client.ts index d80341063..a0da26c0c 100644 --- a/backend/src/api/lightning/clightning/clightning-client.ts +++ b/backend/src/api/lightning/clightning/clightning-client.ts @@ -116,7 +116,7 @@ class LightningError extends Error { const defaultRpcPath = path.join(homedir(), '.lightning') , fStat = (...p) => statSync(path.join(...p)) - , fExists = (...p) => existsSync(path.join(...p)) + , fExists = (...p) => existsSync(path.join(...p)); export default class CLightningClient extends EventEmitter implements AbstractLightningApi { private rpcPath: string; @@ -141,9 +141,9 @@ export default class CLightningClient extends EventEmitter implements AbstractLi // main data directory provided, default to using the bitcoin mainnet subdirectory // to be removed in v0.2.0 else if (fExists(rpcPath, 'bitcoin', 'lightning-rpc')) { - logger.warn(`${rpcPath}/lightning-rpc is missing, using the bitcoin mainnet subdirectory at ${rpcPath}/bitcoin instead.`, logger.tags.ln) - logger.warn(`specifying the main lightning data directory is deprecated, please specify the network directory explicitly.\n`, logger.tags.ln) - rpcPath = path.join(rpcPath, 'bitcoin', 'lightning-rpc') + logger.warn(`${rpcPath}/lightning-rpc is missing, using the bitcoin mainnet subdirectory at ${rpcPath}/bitcoin instead.`, logger.tags.ln); + logger.warn(`specifying the main lightning data directory is deprecated, please specify the network directory explicitly.\n`, logger.tags.ln); + rpcPath = path.join(rpcPath, 'bitcoin', 'lightning-rpc'); } } @@ -249,6 +249,7 @@ export default class CLightningClient extends EventEmitter implements AbstractLi })); } + /** @asyncUnsafe */ async $getNetworkGraph(): Promise { const listnodes: any[] = await this.call('listnodes'); const listchannels: any[] = await this.call('listchannels'); diff --git a/backend/src/api/lightning/clightning/clightning-convert.ts b/backend/src/api/lightning/clightning/clightning-convert.ts index 0db60f649..6fe93df93 100644 --- a/backend/src/api/lightning/clightning/clightning-convert.ts +++ b/backend/src/api/lightning/clightning/clightning-convert.ts @@ -156,6 +156,7 @@ export function convertNode(clNode: any): ILightningApi.Node { /** * Convert clightning "listchannels" response to lnd "describegraph.edges" format + * @asyncUnsafe */ export async function convertAndmergeBidirectionalChannels(clChannels: any[]): Promise { logger.debug(`Converting clightning nodes and channels to lnd graph format`, logger.tags.ln); @@ -212,6 +213,7 @@ export async function convertAndmergeBidirectionalChannels(clChannels: any[]): P /** * Convert two clightning "getchannels" entries into a full a lnd "describegraph.edges" format * In this case, clightning knows the channel policy for both nodes + * @asyncUnsafe */ async function buildFullChannel(clChannelA: any, clChannelB: any): Promise { const lastUpdate = Math.max(clChannelA.last_update ?? 0, clChannelB.last_update ?? 0); @@ -238,6 +240,7 @@ async function buildFullChannel(clChannelA: any, clChannelB: any): Promise { const tx = await FundingTxFetcher.$fetchChannelOpenTx(clChannel.short_channel_id); diff --git a/backend/src/api/lightning/lnd/lnd-api.ts b/backend/src/api/lightning/lnd/lnd-api.ts index f4099e82b..5c3495032 100644 --- a/backend/src/api/lightning/lnd/lnd-api.ts +++ b/backend/src/api/lightning/lnd/lnd-api.ts @@ -40,16 +40,17 @@ class LndApi implements AbstractLightningApi { .then((response) => response.data); } + /** @asyncUnsafe */ async $getNetworkGraph(): Promise { const graph = await axios.get(config.LND.REST_API_URL + '/v1/graph', this.axiosConfig) .then((response) => response.data); for (const node of graph.nodes) { const nodeFeatures: ILightningApi.Feature[] = []; - for (const bit in node.features) { + for (const bit in node.features) { nodeFeatures.push({ bit: parseInt(bit, 10), - name: node.features[bit].name, + name: node.features[bit].name, is_required: node.features[bit].is_required, is_known: node.features[bit].is_known, }); diff --git a/backend/src/api/liquid/elements-parser.ts b/backend/src/api/liquid/elements-parser.ts index 727865b95..083ff6c68 100644 --- a/backend/src/api/liquid/elements-parser.ts +++ b/backend/src/api/liquid/elements-parser.ts @@ -4,13 +4,23 @@ import bitcoinSecondClient from '../bitcoin/bitcoin-second-client'; import { Common } from '../common'; import DB from '../../database'; import logger from '../../logger'; +import * as bitcoinjs from 'bitcoinjs-lib'; -const federationChangeAddresses = ['bc1qxvay4an52gcghxq5lavact7r6qe9l4laedsazz8fj2ee2cy47tlqff4aj4', '3EiAcrzq1cELXScc98KeCswGWZaPGceT1d']; const auditBlockOffsetWithTip = 1; // Wait for 1 block confirmation before processing the block in the audit process to reduce the risk of reorgs +const auditSyncToleranceBlocks = 105; // Audit lag from bitcoin tip to avoid potential unsynced state on peg-ins, which require 102 confirmations +const DYNAFED_ACTIVATION_HEIGHT = 1517040; +const DYNAFED_CHECK_INTERVAL = 20160; +const FEDERATION_TIMELOCK_FALLBACK = 0x0000ffff; // Preventive timelock in case an error occurs while extracting it from the fedpegscript +interface FederationAddress { + fedpegscript: string; + address: string; + timelock: number; + blocknumber: number; +} class ElementsParser { private isRunning = false; - private isUtxosUpdatingRunning = false; + private federationPegScripts: FederationAddress[] = []; constructor() { } @@ -20,15 +30,23 @@ class ElementsParser { } try { this.isRunning = true; + const startedAt = Date.now() / 1000; + const stopAt = startedAt + 3600; // Limit one parse run to 1 hour to keep the tip sufficiently up to date const result = await bitcoinClient.getChainTips(); const tip = result[0].height; const latestBlockHeight = await this.$getLatestBlockHeightFromDatabase(); for (let height = latestBlockHeight + 1; height <= tip; height++) { - const blockHash: IBitcoinApi.ChainTips = await bitcoinClient.getBlockHash(height); + if ((Date.now() / 1000) >= stopAt) { + logger.debug(`Reached max Elements parsing runtime of one hour, pausing parse to resume later`); + break; + } + const blockHash: string = await bitcoinClient.getBlockHash(height); const block: IBitcoinApi.Block = await bitcoinClient.getBlock(blockHash, 2); + await this.$updateFederationPegScripts(block); await this.$parseBlock(block); await this.$saveLatestBlockToDatabase(block.height); } + await this.$updateFederationUtxos(stopAt); this.isRunning = false; } catch (e) { this.isRunning = false; @@ -36,6 +54,7 @@ class ElementsParser { } } + /** @asyncUnsafe */ protected async $parseBlock(block: IBitcoinApi.Block) { for (const tx of block.tx) { await this.$parseInputs(tx, block); @@ -43,6 +62,7 @@ class ElementsParser { } } + /** @asyncUnsafe */ protected async $parseInputs(tx: IBitcoinApi.Transaction, block: IBitcoinApi.Block) { for (const [index, input] of tx.vin.entries()) { if (input.is_pegin) { @@ -51,15 +71,18 @@ class ElementsParser { } } + /** @asyncUnsafe */ protected async $parsePegIn(input: IBitcoinApi.Vin, vindex: number, txid: string, block: IBitcoinApi.Block) { const bitcoinTx: IBitcoinApi.Transaction = await bitcoinSecondClient.getRawTransaction(input.txid, true); const bitcoinBlock: IBitcoinApi.Block = await bitcoinSecondClient.getBlock(bitcoinTx.blockhash); const prevout = bitcoinTx.vout[input.vout || 0]; const outputAddress = prevout.scriptPubKey.address || (prevout.scriptPubKey.addresses && prevout.scriptPubKey.addresses[0]) || ''; + const timelock = await this.$resolvePegInTimelock(input, outputAddress); await this.$savePegToDatabase(block.height, block.time, prevout.value * 100000000, txid, vindex, - outputAddress, bitcoinTx.txid, prevout.n, bitcoinBlock.height, bitcoinBlock.time, 1); + outputAddress, bitcoinTx.txid, prevout.n, bitcoinBlock.height, bitcoinBlock.time, 1, timelock); } + /** @asyncUnsafe */ protected async $parseOutputs(tx: IBitcoinApi.Transaction, block: IBitcoinApi.Block) { for (const output of tx.vout) { if (output.scriptPubKey.pegout_chain) { @@ -74,8 +97,9 @@ class ElementsParser { } } + /** @asyncUnsafe */ protected async $savePegToDatabase(height: number, blockTime: number, amount: number, txid: string, - txindex: number, bitcoinaddress: string, bitcointxid: string, bitcoinindex: number, bitcoinblock: number, bitcoinBlockTime: number, final_tx: number): Promise { + txindex: number, bitcoinaddress: string, bitcointxid: string, bitcoinindex: number, bitcoinblock: number, bitcoinBlockTime: number, final_tx: number, pegInTimelock: number = FEDERATION_TIMELOCK_FALLBACK): Promise { const query = `INSERT IGNORE INTO elements_pegs( block, datetime, amount, txid, txindex, bitcoinaddress, bitcointxid, bitcoinindex, final_tx ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`; @@ -87,27 +111,146 @@ class ElementsParser { logger.debug(`Saved L-BTC peg from Liquid block height #${height} with TXID ${txid}.`); if (amount > 0) { // Peg-in - - // Add the address to the federation addresses table - await DB.query(`INSERT IGNORE INTO federation_addresses (bitcoinaddress) VALUES (?)`, [bitcoinaddress]); - // Add the UTXO to the federation txos table const query_utxos = `INSERT IGNORE INTO federation_txos (txid, txindex, bitcoinaddress, amount, blocknumber, blocktime, unspent, lastblockupdate, lasttimeupdate, timelock, expiredAt, emergencyKey, pegtxid, pegindex, pegblocktime) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`; - const params_utxos: (string | number)[] = [bitcointxid, bitcoinindex, bitcoinaddress, amount, bitcoinblock, bitcoinBlockTime, 1, bitcoinblock - 1, 0, 4032, 0, 0, txid, txindex, blockTime]; + const params_utxos: (string | number)[] = [bitcointxid, bitcoinindex, bitcoinaddress, amount, bitcoinblock, bitcoinBlockTime, 1, bitcoinblock - 1, 0, pegInTimelock, 0, 0, txid, txindex, blockTime]; await DB.query(query_utxos, params_utxos); - const [minBlockUpdate] = await DB.query(`SELECT MIN(lastblockupdate) AS lastblockupdate FROM federation_txos WHERE unspent = 1`) + const [minBlockUpdate] = await DB.query(`SELECT MIN(lastblockupdate) AS lastblockupdate FROM federation_txos WHERE unspent = 1`); await this.$saveLastBlockAuditToDatabase(minBlockUpdate[0]['lastblockupdate']); logger.debug(`Saved new Federation UTXO ${bitcointxid}:${bitcoinindex} belonging to ${bitcoinaddress} to federation txos`); } } + /** @asyncUnsafe */ + private async $getFederationPegScripts(): Promise { + if (this.federationPegScripts.length === 0) { + const [rows] = await DB.query(`SELECT fedpegscript, address, timelock, blocknumber FROM federation_peg_scripts ORDER BY blocknumber DESC`); + this.federationPegScripts = rows as FederationAddress[]; + } + return this.federationPegScripts; + } + + /** @asyncUnsafe */ + private async $updateFederationPegScripts(block: IBitcoinApi.Block): Promise { + const height = block.height; + if (height < DYNAFED_ACTIVATION_HEIGHT || height % DYNAFED_CHECK_INTERVAL !== 0) { + return; + } + + const fedpegscript = block.dynamic_parameters?.current?.fedpegscript as string | undefined; + const fedpegProgram = block.dynamic_parameters?.current?.fedpeg_program as string | undefined; + if (!fedpegscript || !fedpegProgram) { + logger.err(`Missing fedpeg fields at height ${height}, skipping dynamic federation address update.`); + return; + } + + if ((await this.$getFederationPegScripts()).some(entry => entry.fedpegscript === fedpegscript)) { + logger.debug(`Fedpegscript at height ${height} is already known, skipping.`); + return; + } + + logger.debug(`New fedpegscript found at height ${height}, deriving federation address.`); + let address: string; + try { + address = bitcoinjs.address.fromOutputScript(Buffer.from(fedpegProgram, 'hex'), bitcoinjs.networks.bitcoin); + } catch (e) { + logger.err(`Unable to derive federation address from fedpeg program at height ${height}: ${e instanceof Error ? e.message : 'Error'}`); + return; + } + + let timelock = this.extractTimelockFromFedpegscript(fedpegscript); + if (timelock === null) { + timelock = FEDERATION_TIMELOCK_FALLBACK; + logger.err(`Unable to extract federation address timelock from fedpegscript at height ${height}, using fallback timelock ${FEDERATION_TIMELOCK_FALLBACK}.`); + } + + this.federationPegScripts.unshift({ fedpegscript, address, timelock, blocknumber: height }); + logger.debug(`Derived new federation address ${address} with timelock ${timelock} from fedpegscript at height ${height}. Adding to database.`); + try { + await DB.query( + `INSERT INTO federation_peg_scripts (fedpegscript, address, timelock, blocknumber) VALUES (?, ?, ?, ?)`, + [fedpegscript, address, timelock, height] + ); + logger.debug(`Added new federation address ${address} to the database.`); + } catch (e) { + logger.err(`Error while inserting new federation address ${address} into the database: ${e instanceof Error ? e.message : 'Error'}`); + } + } + + private extractTimelockFromFedpegscript(fedpegscript: string): number | null { + const chunks = bitcoinjs.script.decompile(Buffer.from(fedpegscript, 'hex')); + if (!chunks) { + return null; + } + + for (let i = 0; i < chunks.length; i++) { + if (chunks[i] !== bitcoinjs.opcodes.OP_CHECKSEQUENCEVERIFY) { + continue; + } + + const previous = chunks[i - 1]; + if (previous === undefined) { + return null; + } + + if (Buffer.isBuffer(previous)) { + try { + return bitcoinjs.script.number.decode(previous, 5, true); + } catch (e) { + return null; + } + } + + if (typeof previous === 'number') { + if (previous === bitcoinjs.opcodes.OP_0) { + return 0; + } + if (previous >= bitcoinjs.opcodes.OP_1 && previous <= bitcoinjs.opcodes.OP_16) { + return previous - bitcoinjs.opcodes.OP_1 + 1; + } + } + + return null; + } + + return null; + } + + /** @asyncUnsafe */ + private async $resolvePegInTimelock(input: IBitcoinApi.Vin, bitcoinaddress: string): Promise { + const claimScript = input.pegin_witness?.[3]; + if (!claimScript) { + logger.err(`Missing claim_script for peg-in input to address ${bitcoinaddress}, using fallback timelock ${FEDERATION_TIMELOCK_FALLBACK}.`); + return FEDERATION_TIMELOCK_FALLBACK; + } + + // Check claim script against each fedpegscript most recent first + for (const pegScript of (await this.$getFederationPegScripts())) { + try { + const tweakResult = await bitcoinClient.tweakFedPegScript(claimScript, pegScript.fedpegscript); + const matches = tweakResult?.p2wsh === bitcoinaddress || tweakResult?.p2shwsh === bitcoinaddress; + if (matches) { + return pegScript.timelock; + } + } catch (e) { + const message = e instanceof Error ? e.message : String(e); + logger.err(`tweakfedpegscript failed for address ${bitcoinaddress}: ${message}`); + } + } + + logger.err(`No matching fedpegscript found for address ${bitcoinaddress}, using fallback timelock ${FEDERATION_TIMELOCK_FALLBACK}.`); + return FEDERATION_TIMELOCK_FALLBACK; + } + + /** @asyncUnsafe */ protected async $getLatestBlockHeightFromDatabase(): Promise { const query = `SELECT number FROM state WHERE name = 'last_elements_block'`; const [rows] = await DB.query(query); return rows[0]['number']; } + /** @asyncUnsafe */ protected async $saveLatestBlockToDatabase(blockHeight: number) { const query = `UPDATE state SET number = ? WHERE name = 'last_elements_block'`; await DB.query(query, [blockHeight]); @@ -115,19 +258,12 @@ class ElementsParser { ///////////// FEDERATION AUDIT ////////////// - public async $updateFederationUtxos() { - if (this.isUtxosUpdatingRunning) { - return; - } - - this.isUtxosUpdatingRunning = true; - + public async $updateFederationUtxos(stopAt: number) { try { let auditProgress = await this.$getAuditProgress(); // If no peg in transaction was found in the database, return if (!auditProgress.lastBlockAudit) { logger.debug(`No Federation UTXOs found in the database. Waiting for some to be confirmed before starting the Federation UTXOs audit`); - this.isUtxosUpdatingRunning = false; return; } @@ -135,7 +271,6 @@ class ElementsParser { // If the bitcoin blockchain is not synced yet, return if (bitcoinBlocksToSync.bitcoinHeaders > bitcoinBlocksToSync.bitcoinBlocks + 1) { logger.debug(`Bitcoin client is not synced yet. ${bitcoinBlocksToSync.bitcoinHeaders - bitcoinBlocksToSync.bitcoinBlocks} blocks remaining to sync before the Federation audit process can start`); - this.isUtxosUpdatingRunning = false; return; } @@ -154,19 +289,28 @@ class ElementsParser { // Get the peg-out addresses that need to be scanned const redeemAddresses = await this.$getRedeemAddressesToScan(); + const redeemAddressesByAddress = new Map(); + for (const redeemAddress of redeemAddresses) { + const entries = redeemAddressesByAddress.get(redeemAddress.bitcoinaddress); + if (entries) { + entries.push(redeemAddress); + } else { + redeemAddressesByAddress.set(redeemAddress.bitcoinaddress, [redeemAddress]); + } + } // The fast way: check if these UTXOs are still unspent as of the current block with gettxout - let spentAsTip: any[]; - let unspentAsTip: any[]; + let spentAsTip: Map; + let unspentAsTip: Map; if (auditProgress.confirmedTip - auditProgress.lastBlockAudit <= 150) { // If the audit status is not too far in the past, we can use gettxout (fast way) const utxosToParse = await this.$getFederationUtxosToParse(utxos); spentAsTip = utxosToParse.spentAsTip; unspentAsTip = utxosToParse.unspentAsTip; logger.debug(`Found ${utxos.length} Federation UTXOs and ${redeemAddresses.length} Peg-Out Addresses to scan in Bitcoin block height #${auditProgress.lastBlockAudit} / #${auditProgress.confirmedTip}`); - logger.debug(`${unspentAsTip.length} / ${utxos.length} Federation UTXOs are unspent as of tip`); + logger.debug(`${unspentAsTip.size} / ${utxos.length} Federation UTXOs are unspent as of tip`); } else { // If the audit status is too far in the past, it is useless and wasteful to look for still unspent txos since they will all be spent as of the tip - spentAsTip = utxos; - unspentAsTip = []; + spentAsTip = new Map(utxos.map(utxo => [`${utxo.txid}:${utxo.txindex}`, utxo])); + unspentAsTip = new Map(); // Logging const elapsedSeconds = (Date.now() / 1000) - timer; @@ -174,7 +318,7 @@ class ElementsParser { const runningFor = (Date.now() / 1000) - startedAt; const blockPerSeconds = indexedThisRun / elapsedSeconds; indexingSpeeds.push(blockPerSeconds); - if (indexingSpeeds.length > 100) indexingSpeeds.shift(); // Keep the length of the up to 100 last indexing speeds + if (indexingSpeeds.length > 100) {indexingSpeeds.shift();} // Keep the length of the up to 100 last indexing speeds const meanIndexingSpeed = indexingSpeeds.reduce((a, b) => a + b, 0) / indexingSpeeds.length; const eta = (auditProgress.confirmedTip - auditProgress.lastBlockAudit) / meanIndexingSpeed; logger.debug(`Scanning ${utxos.length} Federation UTXOs and ${redeemAddresses.length} Peg-Out Addresses at Bitcoin block height #${auditProgress.lastBlockAudit} / #${auditProgress.confirmedTip} | ~${meanIndexingSpeed.toFixed(2)} blocks/sec | elapsed: ${(runningFor / 60).toFixed(0)} minutes | ETA: ${(eta / 60).toFixed(0)} minutes`); @@ -186,53 +330,60 @@ class ElementsParser { // The slow way: parse the block to look for the spending tx const blockHash: IBitcoinApi.ChainTips = await bitcoinSecondClient.getBlockHash(auditProgress.lastBlockAudit); const block: IBitcoinApi.Block = await bitcoinSecondClient.getBlock(blockHash, 2); - await this.$parseBitcoinBlock(block, spentAsTip, unspentAsTip, auditProgress.confirmedTip, redeemAddresses); + await this.$parseBitcoinBlock(block, spentAsTip, unspentAsTip, auditProgress.confirmedTip, redeemAddressesByAddress); // Finally, update the lastblockupdate of the remaining UTXOs and save to the database - const [minBlockUpdate] = await DB.query(`SELECT MIN(lastblockupdate) AS lastblockupdate FROM federation_txos WHERE unspent = 1`) + const [minBlockUpdate] = await DB.query(`SELECT MIN(lastblockupdate) AS lastblockupdate FROM federation_txos WHERE unspent = 1`); await this.$saveLastBlockAuditToDatabase(minBlockUpdate[0]['lastblockupdate']); auditProgress = await this.$getAuditProgress(); auditProgress.lastBlockAudit++; indexedThisRun++; + + if ((Date.now() / 1000) >= stopAt) { + logger.debug(`Reached max federation audit runtime, pausing audit to let Liquid parsing run and resuming later`); + break; + } } - this.isUtxosUpdatingRunning = false; } catch (e) { - this.isUtxosUpdatingRunning = false; throw new Error(e instanceof Error ? e.message : 'Error'); - } + } } // Get the UTXOs that need to be scanned in block height (UTXOs that were last updated in the block height - 1) - protected async $getFederationUtxosToScan(height: number) { + /** @asyncUnsafe */ + protected async $getFederationUtxosToScan(height: number) { const query = `SELECT txid, txindex, bitcoinaddress, amount, blocknumber, timelock, expiredAt FROM federation_txos WHERE lastblockupdate = ? AND unspent = 1`; const [rows] = await DB.query(query, [height - 1]); return rows as any[]; } // Returns the UTXOs that are spent as of tip and need to be scanned - protected async $getFederationUtxosToParse(utxos: any[]): Promise { - const spentAsTip: any[] = []; - const unspentAsTip: any[] = []; + /** @asyncUnsafe */ + protected async $getFederationUtxosToParse(utxos: any[]): Promise<{ spentAsTip: Map; unspentAsTip: Map }> { + const spentAsTip = new Map(); + const unspentAsTip = new Map(); for (const utxo of utxos) { const result = await bitcoinSecondClient.getTxOut(utxo.txid, utxo.txindex, false); - result ? unspentAsTip.push(utxo) : spentAsTip.push(utxo); + const key = `${utxo.txid}:${utxo.txindex}`; + result ? unspentAsTip.set(key, utxo) : spentAsTip.set(key, utxo); } - + return {spentAsTip, unspentAsTip}; } - protected async $parseBitcoinBlock(block: IBitcoinApi.Block, spentAsTip: any[], unspentAsTip: any[], confirmedTip: number, redeemAddressesData: any[] = []) { - const redeemAddresses: string[] = redeemAddressesData.map(redeemAddress => redeemAddress.bitcoinaddress); + /** @asyncUnsafe */ + protected async $parseBitcoinBlock(block: IBitcoinApi.Block, spentAsTip: Map, unspentAsTip: Map, confirmedTip: number, redeemAddressesByAddress: Map) { + const federationTimelockByAddress = new Map((await this.$getFederationPegScripts()).map(fedAddress => [fedAddress.address, fedAddress.timelock])); for (const tx of block.tx) { let mightRedeemInThisTx = false; // Check if the Federation UTXOs that was spent as of tip are spent in this block for (const input of tx.vin) { - const txo = spentAsTip.find(txo => txo.txid === input.txid && txo.txindex === input.vout); + const txo = spentAsTip.get(`${input.txid!}:${input.vout!}`); if (txo) { - mightRedeemInThisTx = true; // A Federation UTXO is spent in this block: we might find a peg-out address in the outputs + mightRedeemInThisTx = true; // A Federation UTXO is spent in this tx: a peg-out redeem output may be present if (txo.expiredAt > 0 ) { if (input.txinwitness?.length !== 13) { // Check if the witness data of the input contains the 11 signatures: if it doesn't, emergency keys are being used await DB.query(`UPDATE federation_txos SET unspent = 0, lastblockupdate = ?, lasttimeupdate = ?, emergencyKey = 1 WHERE txid = ? AND txindex = ?`, [block.height, block.time, txo.txid, txo.txindex]); @@ -245,36 +396,44 @@ class ElementsParser { await DB.query(`UPDATE federation_txos SET unspent = 0, lastblockupdate = ?, lasttimeupdate = ? WHERE txid = ? AND txindex = ?`, [block.height, block.time, txo.txid, txo.txindex]); logger.debug(`Federation UTXO ${txo.txid}:${txo.txindex} (${txo.amount} sats) was spent in block ${block.height}`); } - // Remove the TXO from the utxo array - spentAsTip.splice(spentAsTip.indexOf(txo), 1); + // Remove the TXO from the map + spentAsTip.delete(`${txo.txid}:${txo.txindex}`); } } // Check if an output is sent to a change address of the federation for (const output of tx.vout) { - if (output.scriptPubKey.address && federationChangeAddresses.includes(output.scriptPubKey.address)) { - // Check that the UTXO was not already added in the DB by previous scans - const [rows_check] = await DB.query(`SELECT txid FROM federation_txos WHERE txid = ? AND txindex = ?`, [tx.txid, output.n]) as any[]; - if (rows_check.length === 0) { - const timelock = output.scriptPubKey.address === federationChangeAddresses[0] ? 4032 : 2016; // P2WSH change address has a 4032 timelock, P2SH change address has a 2016 timelock - const query_utxos = `INSERT INTO federation_txos (txid, txindex, bitcoinaddress, amount, blocknumber, blocktime, unspent, lastblockupdate, lasttimeupdate, timelock, expiredAt, emergencyKey, pegtxid, pegindex, pegblocktime) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`; - const params_utxos: (string | number)[] = [tx.txid, output.n, output.scriptPubKey.address, output.value * 100000000, block.height, block.time, 1, block.height, 0, timelock, 0, 0, '', 0, 0]; - await DB.query(query_utxos, params_utxos); - // Add the UTXO to the utxo array - spentAsTip.push({ - txid: tx.txid, - txindex: output.n, - bitcoinaddress: output.scriptPubKey.address, - amount: output.value * 100000000, - blocknumber: block.height, - timelock: timelock, - expiredAt: 0, - }); - logger.debug(`Added new Federation UTXO ${tx.txid}:${output.n} (${Math.round(output.value * 100000000)} sats), change address: ${output.scriptPubKey.address}`); + if (output.scriptPubKey.address) { + const timelock = federationTimelockByAddress.get(output.scriptPubKey.address); + if (timelock !== undefined) { + // Check that the UTXO was not already added in the DB by previous scans + const [rows_check] = await DB.query(`SELECT txid FROM federation_txos WHERE txid = ? AND txindex = ?`, [tx.txid, output.n]) as any[]; + if (rows_check.length === 0) { + const query_utxos = `INSERT INTO federation_txos (txid, txindex, bitcoinaddress, amount, blocknumber, blocktime, unspent, lastblockupdate, lasttimeupdate, timelock, expiredAt, emergencyKey, pegtxid, pegindex, pegblocktime) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`; + const params_utxos: (string | number)[] = [tx.txid, output.n, output.scriptPubKey.address, output.value * 100000000, block.height, block.time, 1, block.height, 0, timelock, 0, 0, '', 0, 0]; + await DB.query(query_utxos, params_utxos); + // Add the UTXO to the map + spentAsTip.set(`${tx.txid}:${output.n}`, { + txid: tx.txid, + txindex: output.n, + bitcoinaddress: output.scriptPubKey.address, + amount: output.value * 100000000, + blocknumber: block.height, + timelock: timelock, + expiredAt: 0, + }); + logger.debug(`Added new Federation UTXO ${tx.txid}:${output.n} (${Math.round(output.value * 100000000)} sats), change address: ${output.scriptPubKey.address}`); + } } - } - if (mightRedeemInThisTx && output.scriptPubKey.address && redeemAddresses.includes(output.scriptPubKey.address)) { - // Find the number of times output.scriptPubKey.address appears in redeemAddresses. There can be address reuse for peg-outs... - const matchingAddress: any[] = redeemAddressesData.filter(redeemAddress => redeemAddress.bitcoinaddress === output.scriptPubKey.address && -redeemAddress.amount === Math.round(output.value * 100000000)); + + if (!mightRedeemInThisTx) { + continue; + } + const redeemCandidates = redeemAddressesByAddress.get(output.scriptPubKey.address); + if (!redeemCandidates) { + continue; + } + // Find the number of times output.scriptPubKey.address appears in the candidates. There can be address reuse for peg-outs... + const matchingAddress: any[] = redeemCandidates.filter(redeemAddress => -redeemAddress.amount === Math.round(output.value * 100000000)); if (matchingAddress.length > 0) { if (matchingAddress.length > 1) { // If there are more than one peg out address with the same amount, we can't know which one redeemed the UTXO: we take the oldest one @@ -286,9 +445,13 @@ class ElementsParser { const query_add_redeem = `UPDATE elements_pegs SET bitcointxid = ?, bitcoinindex = ? WHERE bitcoinaddress = ? AND amount = ? AND datetime = ?`; const params_add_redeem: (string | number)[] = [tx.txid, output.n, matchingAddress[0].bitcoinaddress, matchingAddress[0].amount, matchingAddress[0].datetime]; await DB.query(query_add_redeem, params_add_redeem); - const index = redeemAddressesData.indexOf(matchingAddress[0]); - redeemAddressesData.splice(index, 1); - redeemAddresses.splice(index, 1); + const index = redeemCandidates.indexOf(matchingAddress[0]); + if (index !== -1) { + redeemCandidates.splice(index, 1); + if (redeemCandidates.length === 0) { + redeemAddressesByAddress.delete(output.scriptPubKey.address); + } + } } else { // The output amount does not match the peg-out amount... log it logger.debug(`Found redeem txid ${tx.txid}:${output.n} to peg-out address ${output.scriptPubKey.address} but output amount ${Math.round(output.value * 100000000)} does not match the peg-out amount!`); } @@ -296,7 +459,7 @@ class ElementsParser { } } - for (const utxo of spentAsTip) { + for (const utxo of spentAsTip.values()) { if (utxo.expiredAt === 0 && block.height >= utxo.blocknumber + utxo.timelock) { // The UTXO is expiring in this block await DB.query(`UPDATE federation_txos SET lastblockupdate = ?, expiredAt = ? WHERE txid = ? AND txindex = ?`, [block.height, block.time, utxo.txid, utxo.txindex]); } else { @@ -304,23 +467,25 @@ class ElementsParser { } } - for (const utxo of unspentAsTip) { + for (const utxo of unspentAsTip.values()) { if (utxo.expiredAt === 0 && block.height >= utxo.blocknumber + utxo.timelock) { // The UTXO is expiring in this block - await DB.query(`UPDATE federation_txos SET unspent = 0, lastblockupdate = ?, expiredAt = ? WHERE txid = ? AND txindex = ?`, [confirmedTip, block.time, utxo.txid, utxo.txindex]); + await DB.query(`UPDATE federation_txos SET lastblockupdate = ?, expiredAt = ? WHERE txid = ? AND txindex = ?`, [confirmedTip, block.time, utxo.txid, utxo.txindex]); } else if (utxo.expiredAt === 0 && confirmedTip >= utxo.blocknumber + utxo.timelock) { // The UTXO is expiring before the tip: we need to keep track of it - await DB.query(`UPDATE federation_txos SET lastblockupdate = ? WHERE txid = ? AND txindex = ?`, [utxo.blocknumber + utxo.timelock - 1, utxo.txid, utxo.txindex]); + await DB.query(`UPDATE federation_txos SET lastblockupdate = ? WHERE txid = ? AND txindex = ?`, [utxo.blocknumber + utxo.timelock - 1, utxo.txid, utxo.txindex]); } else { await DB.query(`UPDATE federation_txos SET lastblockupdate = ? WHERE txid = ? AND txindex = ?`, [confirmedTip, utxo.txid, utxo.txindex]); } } } + /** @asyncUnsafe */ protected async $saveLastBlockAuditToDatabase(blockHeight: number) { const query = `UPDATE state SET number = ? WHERE name = 'last_bitcoin_block_audit'`; await DB.query(query, [blockHeight]); } // Get the bitcoin block where the audit process was last updated + /** @asyncUnsafe */ protected async $getAuditProgress(): Promise { const lastblockaudit = await this.$getLastBlockAudit(); const bitcoinBlocksToSync = await this.$getBitcoinBlockchainState(); @@ -331,20 +496,23 @@ class ElementsParser { } // Get the bitcoin blocks remaining to be synced + /** @asyncUnsafe */ protected async $getBitcoinBlockchainState(): Promise { const result = await bitcoinSecondClient.getBlockchainInfo(); return { bitcoinBlocks: result.blocks, bitcoinHeaders: result.headers, - } + }; } + /** @asyncUnsafe */ protected async $getLastBlockAudit(): Promise { const query = `SELECT number FROM state WHERE name = 'last_bitcoin_block_audit'`; const [rows] = await DB.query(query); return rows[0]['number']; } + /** @asyncUnsafe */ protected async $getRedeemAddressesToScan(): Promise { const query = `SELECT datetime, amount, bitcoinaddress FROM elements_pegs where amount < 0 AND bitcoinaddress != '' AND bitcointxid = '';`; const [rows]: any[] = await DB.query(query); @@ -357,6 +525,7 @@ class ElementsParser { ///////////// DATA QUERY ////////////// + /** @asyncUnsafe */ public async $getAuditStatus(): Promise { const lastBlockAudit = await this.$getLastBlockAudit(); const bitcoinBlocksToSync = await this.$getBitcoinBlockchainState(); @@ -364,16 +533,18 @@ class ElementsParser { bitcoinBlocks: bitcoinBlocksToSync.bitcoinBlocks, bitcoinHeaders: bitcoinBlocksToSync.bitcoinHeaders, lastBlockAudit: lastBlockAudit, - isAuditSynced: bitcoinBlocksToSync.bitcoinHeaders - bitcoinBlocksToSync.bitcoinBlocks <= 2 && bitcoinBlocksToSync.bitcoinBlocks - lastBlockAudit <= 3, + isAuditSynced: bitcoinBlocksToSync.bitcoinHeaders - bitcoinBlocksToSync.bitcoinBlocks <= 3 && bitcoinBlocksToSync.bitcoinBlocks - lastBlockAudit <= auditSyncToleranceBlocks, }; } + /** @asyncUnsafe */ public async $getPegDataByMonth(): Promise { const query = `SELECT SUM(amount) AS amount, DATE_FORMAT(FROM_UNIXTIME(datetime), '%Y-%m-01') AS date FROM elements_pegs GROUP BY DATE_FORMAT(FROM_UNIXTIME(datetime), '%Y%m')`; const [rows] = await DB.query(query); return rows; } + /** @asyncUnsafe */ public async $getFederationReservesByMonth(): Promise { const query = ` SELECT SUM(amount) AS amount, DATE_FORMAT(FROM_UNIXTIME(blocktime), '%Y-%m-01') AS date FROM federation_txos @@ -384,12 +555,13 @@ class ElementsParser { AND (expiredAt = 0 OR expiredAt > UNIX_TIMESTAMP(LAST_DAY(FROM_UNIXTIME(blocktime)) + INTERVAL 1 DAY)) GROUP BY - date;`; + date;`; const [rows] = await DB.query(query); return rows; } // Get the current L-BTC pegs and the last Liquid block it was updated + /** @asyncUnsafe */ public async $getCurrentLbtcSupply(): Promise { const [rows] = await DB.query(`SELECT SUM(amount) AS LBTC_supply FROM elements_pegs;`); const lastblockupdate = await this.$getLatestBlockHeightFromDatabase(); @@ -402,6 +574,7 @@ class ElementsParser { } // Get the current reserves of the federation and the last Bitcoin block it was updated + /** @asyncUnsafe */ public async $getCurrentFederationReserves(): Promise { const [rows] = await DB.query(`SELECT SUM(amount) AS total_balance FROM federation_txos WHERE unspent = 1 AND expiredAt = 0;`); const lastblockaudit = await this.$getLastBlockAudit(); @@ -414,6 +587,7 @@ class ElementsParser { } // Get all of the federation addresses, most balances first + /** @asyncUnsafe */ public async $getFederationAddresses(): Promise { const query = `SELECT bitcoinaddress, SUM(amount) AS balance FROM federation_txos WHERE unspent = 1 AND expiredAt = 0 GROUP BY bitcoinaddress ORDER BY balance DESC;`; const [rows] = await DB.query(query); @@ -421,6 +595,7 @@ class ElementsParser { } // Get all of the UTXOs held by the federation, most recent first + /** @asyncUnsafe */ public async $getFederationUtxos(): Promise { const query = `SELECT txid, txindex, bitcoinaddress, amount, blocknumber, blocktime, pegtxid, pegindex, pegblocktime, timelock, expiredAt FROM federation_txos WHERE unspent = 1 AND expiredAt = 0 ORDER BY blocktime DESC;`; const [rows] = await DB.query(query); @@ -428,6 +603,7 @@ class ElementsParser { } // Get expired UTXOs, most recent first + /** @asyncUnsafe */ public async $getExpiredUtxos(): Promise { const query = `SELECT txid, txindex, bitcoinaddress, amount, blocknumber, blocktime, pegtxid, pegindex, pegblocktime, timelock, expiredAt FROM federation_txos WHERE unspent = 1 AND expiredAt > 0 ORDER BY blocktime DESC;`; const [rows]: any[] = await DB.query(query); @@ -439,13 +615,15 @@ class ElementsParser { } // Get utxos that were spent using emergency keys + /** @asyncUnsafe */ public async $getEmergencySpentUtxos(): Promise { const query = `SELECT txid, txindex, bitcoinaddress, amount, blocknumber, blocktime, pegtxid, pegindex, pegblocktime, timelock, expiredAt FROM federation_txos WHERE emergencyKey = 1 ORDER BY blocktime DESC;`; const [rows] = await DB.query(query); return rows; } - + // Get the total number of federation addresses + /** @asyncUnsafe */ public async $getFederationAddressesNumber(): Promise { const query = `SELECT COUNT(DISTINCT bitcoinaddress) AS address_count FROM federation_txos WHERE unspent = 1 AND expiredAt = 0;`; const [rows] = await DB.query(query); @@ -453,6 +631,7 @@ class ElementsParser { } // Get the total number of federation utxos + /** @asyncUnsafe */ public async $getFederationUtxosNumber(): Promise { const query = `SELECT COUNT(*) AS utxo_count FROM federation_txos WHERE unspent = 1 AND expiredAt = 0;`; const [rows] = await DB.query(query); @@ -460,6 +639,7 @@ class ElementsParser { } // Get the total number of emergency spent utxos and their total amount + /** @asyncUnsafe */ public async $getEmergencySpentUtxosStats(): Promise { const query = `SELECT COUNT(*) AS utxo_count, SUM(amount) AS total_amount FROM federation_txos WHERE emergencyKey = 1;`; const [rows] = await DB.query(query); @@ -467,6 +647,7 @@ class ElementsParser { } // Get recent pegs in / out + /** @asyncUnsafe */ public async $getPegsList(count: number = 0): Promise { const query = `SELECT txid, txindex, amount, bitcoinaddress, bitcointxid, bitcoinindex, datetime AS blocktime FROM elements_pegs ORDER BY block DESC LIMIT 15 OFFSET ?;`; const [rows] = await DB.query(query, [count]); @@ -474,6 +655,7 @@ class ElementsParser { } // Get all peg in / out from the last month + /** @asyncUnsafe */ public async $getPegsVolumeDaily(): Promise { const pegInQuery = await DB.query(`SELECT SUM(amount) AS volume, COUNT(*) AS number FROM elements_pegs WHERE amount > 0 and datetime > UNIX_TIMESTAMP(TIMESTAMPADD(DAY, -1, CURRENT_TIMESTAMP()));`); const pegOutQuery = await DB.query(`SELECT SUM(amount) AS volume, COUNT(*) AS number FROM elements_pegs WHERE amount < 0 and datetime > UNIX_TIMESTAMP(TIMESTAMPADD(DAY, -1, CURRENT_TIMESTAMP()));`); @@ -484,6 +666,7 @@ class ElementsParser { } // Get the total pegs number + /** @asyncUnsafe */ public async $getPegsCount(): Promise { const [rows] = await DB.query(`SELECT COUNT(*) AS pegs_count FROM elements_pegs;`); return rows[0]; diff --git a/backend/src/api/liquid/liquid.routes.ts b/backend/src/api/liquid/liquid.routes.ts index 563cbaced..de728da6a 100644 --- a/backend/src/api/liquid/liquid.routes.ts +++ b/backend/src/api/liquid/liquid.routes.ts @@ -6,6 +6,8 @@ import icons from './icons'; import { handleError } from '../../utils/api'; import PricesRepository from '../../repositories/PricesRepository'; +const PROXY_PATH_SEGMENT_REGEX = /^(?!\.{1,2}$)[^\p{Cc}/?#\\]{1,256}$/u; + class LiquidRoutes { public initRoutes(app: Application) { app @@ -14,7 +16,7 @@ class LiquidRoutes { .get(config.MEMPOOL.API_URL_PREFIX + 'asset/:assetId/icon', this.getLiquidIcon) .get(config.MEMPOOL.API_URL_PREFIX + 'assets/group/:id', this.$getAssetGroup) ; - + if (config.DATABASE.ENABLED) { app .get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs', this.$getElementsPegs) @@ -68,8 +70,13 @@ class LiquidRoutes { } private async $getAssetGroup(req: Request, res: Response) { + if (!PROXY_PATH_SEGMENT_REGEX.test(req.params.id)) { + handleError(req, res, 400, 'Invalid asset group id'); + return; + } + try { - const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.LIQUID_API}/assets/group/${parseInt(req.params.id, 10)}`, + const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.LIQUID_API}/assets/group/${encodeURIComponent(req.params.id)}`, { responseType: 'stream', timeout: 10000 }); response.data.pipe(res); } catch (e) { @@ -262,7 +269,7 @@ class LiquidRoutes { res.header('Pragma', 'public'); res.header('Cache-control', 'public'); res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString()); - if (['testnet', 'signet', 'liquidtestnet', 'testnet4'].includes(config.MEMPOOL.NETWORK)) { + if (['testnet', 'signet', 'liquidtestnet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) { handleError(req, res, 400, 'Prices are not available on testnets.'); return; } diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts index c55e06568..17ccf84e6 100644 --- a/backend/src/api/mempool-blocks.ts +++ b/backend/src/api/mempool-blocks.ts @@ -8,6 +8,7 @@ import path from 'path'; import mempool from './mempool'; import { Acceleration } from './services/acceleration'; import PoolsRepository from '../repositories/PoolsRepository'; +import { ProjectedBlock } from '../cluster-mempool/cluster-mempool'; const MAX_UINT32 = Math.pow(2, 32) - 1; @@ -45,8 +46,9 @@ class MempoolBlocks { return this.mempoolBlockDeltas; } + /** @asyncUnsafe */ public async updatePools$(): Promise { - if (['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK) === false) { + if (['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK) === false) { this.pools = {}; return; } @@ -98,6 +100,7 @@ class MempoolBlocks { return mempoolBlockDeltas; } + /** @asyncSafe */ public async $makeBlockTemplates(transactions: string[], newMempool: { [txid: string]: MempoolTransactionExtended }, candidates: GbtCandidates | undefined, saveResults: boolean = false, useAccelerations: boolean = false, accelerationPool?: number): Promise { const start = Date.now(); @@ -172,6 +175,7 @@ class MempoolBlocks { return this.mempoolBlocks; } + /** @asyncSafe */ public async $updateBlockTemplates(transactions: string[], newMempool: { [txid: string]: MempoolTransactionExtended }, added: MempoolTransactionExtended[], removed: MempoolTransactionExtended[], candidates: GbtCandidates | undefined, accelerationDelta: string[] = [], saveResults: boolean = false, useAccelerations: boolean = false): Promise { if (!this.txSelectionWorker) { // need to reset the worker @@ -228,12 +232,14 @@ class MempoolBlocks { } } + /** @asyncSafe */ private resetRustGbt(): void { this.rustInitialized = false; this.rustGbtGenerator = new GbtGenerator(config.MEMPOOL.BLOCK_WEIGHT_UNITS, config.MEMPOOL.MEMPOOL_BLOCKS_AMOUNT); } - public async $rustMakeBlockTemplates(txids: string[], newMempool: { [txid: string]: MempoolTransactionExtended }, candidates: GbtCandidates | undefined, saveResults: boolean = false, useAccelerations: boolean = false, accelerationPool?: number): Promise { + /** @asyncSafe */ + public async $rustMakeBlockTemplates(txids: string[], newMempool: { [txid: string]: MempoolTransactionExtended }, candidates: GbtCandidates | undefined, saveResults: boolean = false, useAccelerations: boolean = false, accelerationPool?: number, dryRun = false): Promise { const start = Date.now(); // reset mempool short ids @@ -273,7 +279,7 @@ class MempoolBlocks { const expectedSize = transactions.length; const resultMempoolSize = blocks.reduce((total, block) => total + block.length, 0) + overflow.length; logger.debug(`RUST updateBlockTemplates returned ${resultMempoolSize} txs out of ${expectedSize} in the mempool, ${overflow.length} were unmineable`); - const processed = this.processBlockTemplates(newMempool, blocks, blockWeights, rates, clusters, candidates, accelerations, accelerationPool, saveResults); + const processed = this.processBlockTemplates(newMempool, blocks, blockWeights, rates, clusters, candidates, accelerations, accelerationPool, saveResults, dryRun); logger.debug(`RUST makeBlockTemplates completed in ${(Date.now() - start)/1000} seconds`); return processed; } catch (e) { @@ -285,20 +291,21 @@ class MempoolBlocks { return this.mempoolBlocks; } + /** @asyncSafe */ public async $oneOffRustBlockTemplates(transactions: string[], newMempool: { [txid: string]: MempoolTransactionExtended }, candidates: GbtCandidates | undefined, useAccelerations: boolean, accelerationPool?: number): Promise { return this.$rustMakeBlockTemplates(transactions, newMempool, candidates, false, useAccelerations, accelerationPool); } - public async $rustUpdateBlockTemplates(transactions: string[], newMempool: { [txid: string]: MempoolTransactionExtended }, added: MempoolTransactionExtended[], removed: MempoolTransactionExtended[], candidates: GbtCandidates | undefined, useAccelerations: boolean, accelerationPool?: number): Promise { + /** @asyncSafe */ + public async $rustUpdateBlockTemplates(transactions: string[], newMempool: { [txid: string]: MempoolTransactionExtended }, added: MempoolTransactionExtended[], removed: MempoolTransactionExtended[], candidates: GbtCandidates | undefined, useAccelerations: boolean, accelerationPool?: number, dryRun = false): Promise { // GBT optimization requires that uids never get too sparse // as a sanity check, we should also explicitly prevent uint32 uid overflow if (this.nextUid + added.length >= Math.min(Math.max(262144, 2 * transactions.length), MAX_UINT32)) { this.resetRustGbt(); } - if (!this.rustInitialized) { - // need to reset the worker - return this.$rustMakeBlockTemplates(transactions, newMempool, candidates, true, useAccelerations, accelerationPool); + if (!this.rustInitialized || dryRun) { + return this.$rustMakeBlockTemplates(transactions, newMempool, candidates, !dryRun, useAccelerations, accelerationPool, dryRun); } const start = Date.now(); @@ -337,7 +344,7 @@ class MempoolBlocks { if (transactions.length !== resultMempoolSize) { throw new Error(`GBT returned wrong number of transactions ${transactions.length} vs ${resultMempoolSize}, cache is probably out of sync`); } else { - const processed = this.processBlockTemplates(newMempool, blocks, blockWeights, rates, clusters, candidates, accelerations, accelerationPool, true); + const processed = this.processBlockTemplates(newMempool, blocks, blockWeights, rates, clusters, candidates, accelerations, accelerationPool, !dryRun, dryRun); this.removeUids(removedTxs); logger.debug(`RUST updateBlockTemplates completed in ${(Date.now() - start)/1000} seconds`); return processed; @@ -349,20 +356,22 @@ class MempoolBlocks { } } - private processBlockTemplates(mempool: { [txid: string]: MempoolTransactionExtended }, blocks: string[][], blockWeights: number[] | null, rates: [string, number][], clusters: string[][], candidates: GbtCandidates | undefined, accelerations: { [txid: string]: Acceleration }, accelerationPool, saveResults): MempoolBlockWithTransactions[] { - for (const txid of Object.keys(candidates?.txs ?? mempool)) { - if (txid in mempool) { - mempool[txid].cpfpDirty = false; - mempool[txid].ancestors = []; - mempool[txid].descendants = []; - mempool[txid].bestDescendant = null; + private processBlockTemplates(mempool: { [txid: string]: MempoolTransactionExtended }, blocks: string[][], blockWeights: number[] | null, rates: [string, number][], clusters: string[][], candidates: GbtCandidates | undefined, accelerations: { [txid: string]: Acceleration }, accelerationPool, saveResults, dryRun = false): MempoolBlockWithTransactions[] { + if (!dryRun) { + for (const txid of Object.keys(candidates?.txs ?? mempool)) { + if (txid in mempool) { + mempool[txid].cpfpDirty = false; + mempool[txid].ancestors = []; + mempool[txid].descendants = []; + mempool[txid].bestDescendant = null; + } } - } - for (const [txid, rate] of rates) { - if (txid in mempool) { - mempool[txid].cpfpDirty = (rate !== mempool[txid].effectiveFeePerVsize); - mempool[txid].effectiveFeePerVsize = rate; - mempool[txid].cpfpChecked = true; + for (const [txid, rate] of rates) { + if (txid in mempool) { + mempool[txid].cpfpDirty = (rate !== mempool[txid].effectiveFeePerVsize); + mempool[txid].effectiveFeePerVsize = rate; + mempool[txid].cpfpChecked = true; + } } } @@ -380,58 +389,60 @@ class MempoolBlocks { feeStatsCalculator = new OnlineFeeStatsCalculator(stackWeight, 0.5, [10, 20, 30, 40, 50, 60, 70, 80, 90]); } - const ancestors: Ancestor[] = []; - const descendants: Ancestor[] = []; - let ancestor: MempoolTransactionExtended; - for (const cluster of clusters) { - for (const memberTxid of cluster) { - const mempoolTx = mempool[memberTxid]; - if (mempoolTx) { - // ugly micro-optimization to avoid allocating new arrays - ancestors.length = 0; - descendants.length = 0; - let matched = false; - cluster.forEach(txid => { - ancestor = mempool[txid]; - if (txid === memberTxid) { - matched = true; - } else { - if (!ancestor) { - console.log('txid missing from mempool! ', txid, candidates?.txs[txid]); - return; - } - const relative = { - txid: txid, - fee: ancestor.fee, - weight: (ancestor.adjustedVsize * 4), - }; - if (matched) { - descendants.push(relative); - if (!mempoolTx.lastBoosted || (ancestor.firstSeen && ancestor.firstSeen > mempoolTx.lastBoosted)) { - mempoolTx.lastBoosted = ancestor.firstSeen; - } + if (!dryRun) { + const ancestors: Ancestor[] = []; + const descendants: Ancestor[] = []; + let ancestor: MempoolTransactionExtended; + for (const cluster of clusters) { + for (const memberTxid of cluster) { + const mempoolTx = mempool[memberTxid]; + if (mempoolTx) { + // ugly micro-optimization to avoid allocating new arrays + ancestors.length = 0; + descendants.length = 0; + let matched = false; + cluster.forEach(txid => { + ancestor = mempool[txid]; + if (txid === memberTxid) { + matched = true; } else { - ancestors.push(relative); + if (!ancestor) { + console.log('txid missing from mempool! ', txid, candidates?.txs[txid]); + return; + } + const relative = { + txid: txid, + fee: ancestor.fee, + weight: (ancestor.adjustedVsize * 4), + }; + if (matched) { + descendants.push(relative); + if (!mempoolTx.lastBoosted || (ancestor.firstSeen && ancestor.firstSeen > mempoolTx.lastBoosted)) { + mempoolTx.lastBoosted = ancestor.firstSeen; + } + } else { + ancestors.push(relative); + } } + }); + if (mempoolTx.ancestors?.length !== ancestors.length || mempoolTx.descendants?.length !== descendants.length) { + mempoolTx.cpfpDirty = true; } - }); - if (mempoolTx.ancestors?.length !== ancestors.length || mempoolTx.descendants?.length !== descendants.length) { - mempoolTx.cpfpDirty = true; + // ugly micro-optimization to avoid allocating new arrays or objects + if (mempoolTx.ancestors) { + mempoolTx.ancestors.length = 0; + } else { + mempoolTx.ancestors = []; + } + if (mempoolTx.descendants) { + mempoolTx.descendants.length = 0; + } else { + mempoolTx.descendants = []; + } + mempoolTx.ancestors.push(...ancestors); + mempoolTx.descendants.push(...descendants); + mempoolTx.cpfpChecked = true; } - // ugly micro-optimization to avoid allocating new arrays or objects - if (mempoolTx.ancestors) { - mempoolTx.ancestors.length = 0; - } else { - mempoolTx.ancestors = []; - } - if (mempoolTx.descendants) { - mempoolTx.descendants.length = 0; - } else { - mempoolTx.descendants = []; - } - mempoolTx.ancestors.push(...ancestors); - mempoolTx.descendants.push(...descendants); - mempoolTx.cpfpChecked = true; } } } @@ -464,34 +475,42 @@ class MempoolBlocks { const txid = block[i]; if (txid in mempool) { mempoolTx = mempool[txid]; - // save position in projected blocks - mempoolTx.position = { - block: blockIndex, - vsize: totalVsize + (mempoolTx.vsize / 2), - }; - if (txid in accelerations) { - acceleration = accelerations[txid]; - if (isAcceleratedBy[txid] || (acceleration && (!accelerationPool || acceleration.pools.includes(accelerationPool)))) { - if (!mempoolTx.acceleration) { - mempoolTx.cpfpDirty = true; - } - mempoolTx.acceleration = true; - mempoolTx.acceleratedBy = isAcceleratedBy[txid] || acceleration?.pools; - mempoolTx.acceleratedAt = acceleration?.added; - mempoolTx.feeDelta = acceleration?.feeDelta; - for (const ancestor of mempoolTx.ancestors || []) { - if (!(ancestor.txid in mempool)) { - continue; + if (!dryRun) { + // save position in projected blocks + mempoolTx.position = { + block: blockIndex, + vsize: totalVsize + (mempoolTx.vsize / 2), + }; + + if (txid in accelerations) { + acceleration = accelerations[txid]; + if (isAcceleratedBy[txid] || (acceleration && (!accelerationPool || acceleration.pools.includes(accelerationPool)))) { + if (!mempoolTx.acceleration) { + mempoolTx.cpfpDirty = true; } - if (!mempool[ancestor.txid].acceleration) { - mempool[ancestor.txid].cpfpDirty = true; + mempoolTx.acceleration = true; + mempoolTx.acceleratedBy = isAcceleratedBy[txid] || acceleration?.pools; + mempoolTx.acceleratedAt = acceleration?.added; + mempoolTx.feeDelta = acceleration?.feeDelta; + for (const ancestor of mempoolTx.ancestors || []) { + if (!(ancestor.txid in mempool)) { + continue; + } + if (!mempool[ancestor.txid].acceleration) { + mempool[ancestor.txid].cpfpDirty = true; + } + mempool[ancestor.txid].acceleration = true; + mempool[ancestor.txid].acceleratedBy = mempoolTx.acceleratedBy; + mempool[ancestor.txid].acceleratedAt = mempoolTx.acceleratedAt; + mempool[ancestor.txid].feeDelta = mempoolTx.feeDelta; + isAcceleratedBy[ancestor.txid] = mempoolTx.acceleratedBy; + } + } else { + if (mempoolTx.acceleration) { + mempoolTx.cpfpDirty = true; + delete mempoolTx.acceleration; } - mempool[ancestor.txid].acceleration = true; - mempool[ancestor.txid].acceleratedBy = mempoolTx.acceleratedBy; - mempool[ancestor.txid].acceleratedAt = mempoolTx.acceleratedAt; - mempool[ancestor.txid].feeDelta = mempoolTx.feeDelta; - isAcceleratedBy[ancestor.txid] = mempoolTx.acceleratedBy; } } else { if (mempoolTx.acceleration) { @@ -499,11 +518,6 @@ class MempoolBlocks { delete mempoolTx.acceleration; } } - } else { - if (mempoolTx.acceleration) { - mempoolTx.cpfpDirty = true; - delete mempoolTx.acceleration; - } } // online calculation of stack-of-blocks fee stats @@ -541,6 +555,115 @@ class MempoolBlocks { return mempoolBlocks; } + public processClusterMempoolBlocks(projectedBlocks: ProjectedBlock[], newMempool: { [txid: string]: MempoolTransactionExtended }, accelerations: { [txid: string]: Acceleration }, saveResults = true, accelerationPool?: number): MempoolBlockWithTransactions[] { + const lastBlockIndex = projectedBlocks.length - 1; + let hasBlockStack = projectedBlocks.length >= 8; + let stackWeight = 0; + let feeStatsCalculator: OnlineFeeStatsCalculator | null = null; + if (hasBlockStack) { + stackWeight = projectedBlocks[lastBlockIndex].weight; + hasBlockStack = stackWeight > config.MEMPOOL.BLOCK_WEIGHT_UNITS; + feeStatsCalculator = new OnlineFeeStatsCalculator(stackWeight, 0.5, [10, 20, 30, 40, 50, 60, 70, 80, 90]); + } + + const isAcceleratedBy: { [txid: string]: number[] | false } = {}; + + const sizeLimit = (config.MEMPOOL.BLOCK_WEIGHT_UNITS / 4) * 1.2; + let mempoolTx: MempoolTransactionExtended; + let acceleration: Acceleration; + const mempoolBlocks: MempoolBlockWithTransactions[] = []; + + for (let blockIndex = 0; blockIndex < projectedBlocks.length; blockIndex++) { + const projected = projectedBlocks[blockIndex]; + let totalSize = 0; + let totalVsize = 0; + let totalWeight = 0; + let totalFees = 0; + const transactions: MempoolTransactionExtended[] = []; + const validTxids: string[] = []; + + for (const txid of projected.txids) { + if (txid in newMempool) { + mempoolTx = newMempool[txid]; + validTxids.push(txid); + + // save position in projected blocks + mempoolTx.position = { + block: blockIndex, + vsize: totalVsize + (mempoolTx.vsize / 2), + }; + + if (txid in accelerations) { + acceleration = accelerations[txid]; + if (isAcceleratedBy[txid] || (acceleration && (!accelerationPool || acceleration.pools.includes(accelerationPool)))) { + if (!mempoolTx.acceleration) { + mempoolTx.cpfpDirty = true; + } + mempoolTx.acceleration = true; + mempoolTx.acceleratedBy = isAcceleratedBy[txid] || acceleration?.pools; + mempoolTx.acceleratedAt = acceleration?.added; + mempoolTx.feeDelta = acceleration?.feeDelta; + for (const ancestor of mempoolTx.ancestors || []) { + if (!(ancestor.txid in newMempool)) { + continue; + } + if (!newMempool[ancestor.txid].acceleration) { + newMempool[ancestor.txid].cpfpDirty = true; + } + newMempool[ancestor.txid].acceleration = true; + newMempool[ancestor.txid].acceleratedBy = mempoolTx.acceleratedBy; + newMempool[ancestor.txid].acceleratedAt = mempoolTx.acceleratedAt; + newMempool[ancestor.txid].feeDelta = mempoolTx.feeDelta; + isAcceleratedBy[ancestor.txid] = mempoolTx.acceleratedBy; + } + } else { + if (mempoolTx.acceleration) { + mempoolTx.cpfpDirty = true; + delete mempoolTx.acceleration; + } + } + } else { + if (mempoolTx.acceleration) { + mempoolTx.cpfpDirty = true; + delete mempoolTx.acceleration; + } + } + + if (hasBlockStack && blockIndex === lastBlockIndex && feeStatsCalculator) { + feeStatsCalculator.processNext(mempoolTx); + } + + totalSize += mempoolTx.size; + totalVsize += mempoolTx.vsize; + totalWeight += mempoolTx.weight; + totalFees += mempoolTx.fee; + + if (totalVsize <= sizeLimit) { + transactions.push(mempoolTx); + } + } + } + + mempoolBlocks[blockIndex] = this.dataToMempoolBlocks( + validTxids, + transactions, + totalSize, + totalWeight, + totalFees, + (hasBlockStack && blockIndex === lastBlockIndex && feeStatsCalculator) ? feeStatsCalculator.getRawFeeStats() : undefined, + ); + } + + if (saveResults) { + const deltas = this.calculateMempoolDeltas(this.mempoolBlocks, mempoolBlocks); + this.mempoolBlocks = mempoolBlocks; + this.mempoolBlockDeltas = deltas; + this.updateAccelerationPositions(newMempool, accelerations, mempoolBlocks); + } + + return mempoolBlocks; + } + private dataToMempoolBlocks(transactionIds: string[], transactions: MempoolTransactionExtended[], totalSize: number, totalWeight: number, totalFees: number, feeStats?: EffectiveFeeStats ): MempoolBlockWithTransactions { if (!feeStats) { feeStats = Common.calcEffectiveFeeStatistics(transactions); diff --git a/backend/src/api/mempool.ts b/backend/src/api/mempool.ts index 34a27f510..bee3e10db 100644 --- a/backend/src/api/mempool.ts +++ b/backend/src/api/mempool.ts @@ -13,6 +13,7 @@ import { Acceleration } from './services/acceleration'; import accelerationApi from './services/acceleration'; import redisCache from './redis-cache'; import blocks from './blocks'; +import { ClusterMempool } from '../cluster-mempool/cluster-mempool'; class Mempool { private inSync: boolean = false; @@ -22,6 +23,7 @@ class Mempool { private spendMap = new Map(); private recentlyDeleted: MempoolTransactionExtended[][] = []; // buffer of transactions deleted in recent mempool updates private mempoolInfo: IBitcoinApi.MempoolInfo; + public clusterMempool: ClusterMempool | null = null; private mempoolChangedCallback: ((newMempool: {[txId: string]: MempoolTransactionExtended; }, newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[][], accelerationDelta: string[]) => void) | undefined; private $asyncMempoolChangedCallback: ((newMempool: {[txId: string]: MempoolTransactionExtended; }, mempoolSize: number, newTransactions: MempoolTransactionExtended[], @@ -38,7 +40,7 @@ class Mempool { private mempoolProtection = 0; private latestTransactions: any[] = []; - private ESPLORA_MISSING_TX_WARNING_THRESHOLD = 100; + private ESPLORA_MISSING_TX_WARNING_THRESHOLD = 100; private SAMPLE_TIME = 10000; // In ms private timer = new Date().getTime(); private missingTxCount = 0; @@ -51,17 +53,20 @@ class Mempool { // Initialize mempoolInfo here to avoid circular dependency issues // Use config directly instead of Common.isLiquid() to break circular dependency const isLiquid = config.MEMPOOL.NETWORK === 'liquid' || config.MEMPOOL.NETWORK === 'liquidtestnet'; - this.mempoolInfo = { - loaded: false, - size: 0, - bytes: 0, - usage: 0, + this.mempoolInfo = { + loaded: false, + size: 0, + bytes: 0, + usage: 0, total_fee: 0, - maxmempool: 300000000, - mempoolminfee: isLiquid ? 0.00000100 : 0.00001000, - minrelaytxfee: isLiquid ? 0.00000100 : 0.00001000 + maxmempool: 300000000, + mempoolminfee: isLiquid ? 0.00000100 : 0.00001000, + minrelaytxfee: isLiquid ? 0.00000100 : 0.00001000 }; this.txPerSecondInterval = setInterval(this.updateTxPerSecond.bind(this), 1000); + if (config.MEMPOOL.CLUSTER_MEMPOOL) { + this.clusterMempool = new ClusterMempool(this.mempoolCache, this.accelerations); + } } /** @@ -122,6 +127,7 @@ class Mempool { return this.spendMap.get(`${txid}:${index}`); } + /** @asyncUnsafe */ public async $setMempool(mempoolData: { [txId: string]: MempoolTransactionExtended }) { this.mempoolCache = mempoolData; let count = 0; @@ -152,6 +158,9 @@ class Mempool { await redisCache.$flushTransactions(); logger.debug(`Finished migrating cache transactions in ${((Date.now() - redisTimer) / 1000).toFixed(2)} seconds`); } + if (config.MEMPOOL.CLUSTER_MEMPOOL) { + this.clusterMempool = new ClusterMempool(this.mempoolCache, this.accelerations); + } if (this.mempoolChangedCallback) { this.mempoolChangedCallback(this.mempoolCache, [], [], []); } @@ -203,6 +212,7 @@ class Mempool { return this.mempoolCandidates; } + /** @asyncUnsafe */ public async $updateMemPoolInfo() { this.mempoolInfo = await this.$getMempoolInfo(); } @@ -232,6 +242,7 @@ class Mempool { return txTimes; } + /** @asyncUnsafe */ public async $updateMempool(transactions: string[], accelerations: Record | null, minFeeMempool: string[], minFeeTip: number, pollRate: number): Promise { logger.debug(`Updating mempool...`); @@ -371,6 +382,7 @@ class Mempool { for (const tx of deletedTransactions) { delete this.mempoolCache[tx.txid]; } + redisCache.queueTransactionsForRemoval(deletedTransactions.map(tx => tx.txid)); } const candidates = await this.getNextCandidates(minFeeMempool, minFeeTip, deletedTransactions); @@ -384,6 +396,14 @@ class Mempool { hasChange = true; } + if (config.MEMPOOL.CLUSTER_MEMPOOL && (newTransactions.length || deletedTransactions.length || accelerationDelta.length)) { + this.clusterMempool?.applyMempoolChange({ + added: newTransactions, + removed: deletedTransactions, + accelerations: this.getAccelerations(), + }); + } + this.mempoolCacheDelta = Math.abs(transactions.length - newMempoolSize); const candidatesChanged = candidates?.added?.length || candidates?.removed?.length; @@ -409,7 +429,7 @@ class Mempool { // Update Redis cache if (config.REDIS.ENABLED) { await redisCache.$flushTransactions(); - await redisCache.$removeTransactions(deletedTransactions.map(tx => tx.txid)); + await redisCache.$removeTransactions(); await rbfCache.updateCache(); } diff --git a/backend/src/api/mining/mining-routes.ts b/backend/src/api/mining/mining-routes.ts index 806b113f1..47a6b8f0a 100644 --- a/backend/src/api/mining/mining-routes.ts +++ b/backend/src/api/mining/mining-routes.ts @@ -1,12 +1,12 @@ import { Application, Request, Response } from 'express'; -import config from "../../config"; +import config from '../../config'; import logger from '../../logger'; import BlocksAuditsRepository from '../../repositories/BlocksAuditsRepository'; import BlocksRepository from '../../repositories/BlocksRepository'; import DifficultyAdjustmentsRepository from '../../repositories/DifficultyAdjustmentsRepository'; import HashratesRepository from '../../repositories/HashratesRepository'; import bitcoinClient from '../bitcoin/bitcoin-client'; -import mining from "./mining"; +import mining from './mining'; import PricesRepository from '../../repositories/PricesRepository'; import AccelerationRepository from '../../repositories/AccelerationRepository'; import accelerationApi from '../services/acceleration'; @@ -53,7 +53,7 @@ class MiningRoutes { res.header('Pragma', 'public'); res.header('Cache-control', 'public'); res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString()); - if (['testnet', 'signet', 'liquidtestnet', 'testnet4'].includes(config.MEMPOOL.NETWORK)) { + if (['testnet', 'signet', 'liquidtestnet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) { handleError(req, res, 400, 'Prices are not available on testnets.'); return; } @@ -394,7 +394,7 @@ class MiningRoutes { res.header('Pragma', 'public'); res.header('Cache-control', 'public'); res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); - if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid', 'testnet4'].includes(config.MEMPOOL.NETWORK)) { + if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) { handleError(req, res, 400, 'Acceleration data is not available.'); return; } @@ -409,7 +409,7 @@ class MiningRoutes { res.header('Pragma', 'public'); res.header('Cache-control', 'public'); res.setHeader('Expires', new Date(Date.now() + 1000 * 3600 * 24).toUTCString()); - if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid', 'testnet4'].includes(config.MEMPOOL.NETWORK)) { + if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) { handleError(req, res, 400, 'Acceleration data is not available.'); return; } @@ -425,7 +425,7 @@ class MiningRoutes { res.header('Pragma', 'public'); res.header('Cache-control', 'public'); res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); - if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid', 'testnet4'].includes(config.MEMPOOL.NETWORK)) { + if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) { handleError(req, res, 400, 'Acceleration data is not available.'); return; } @@ -440,7 +440,7 @@ class MiningRoutes { res.header('Pragma', 'public'); res.header('Cache-control', 'public'); res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); - if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid', 'testnet4'].includes(config.MEMPOOL.NETWORK)) { + if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) { handleError(req, res, 400, 'Acceleration data is not available.'); return; } @@ -455,7 +455,7 @@ class MiningRoutes { res.header('Pragma', 'public'); res.header('Cache-control', 'public'); res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); - if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid', 'testnet4'].includes(config.MEMPOOL.NETWORK)) { + if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) { handleError(req, res, 400, 'Acceleration data is not available.'); return; } diff --git a/backend/src/api/mining/mining.ts b/backend/src/api/mining/mining.ts index 2b007e9c5..ebf8fd40d 100644 --- a/backend/src/api/mining/mining.ts +++ b/backend/src/api/mining/mining.ts @@ -26,7 +26,7 @@ class Mining { private blocksPriceIndexingRunning = false; public lastHashrateIndexingDate: number | null = null; public lastWeeklyHashrateIndexingDate: number | null = null; - + public reindexHashrateRequested = false; public reindexDifficultyAdjustmentRequested = false; @@ -66,7 +66,7 @@ class Mining { {from, to} ); } - + /** * Get historical block rewards */ @@ -114,31 +114,29 @@ class Mining { const poolsStatistics = {}; const poolsInfo: PoolInfo[] = await PoolsRepository.$getPoolsInfo(interval); - const emptyBlocks: any[] = await BlocksRepository.$countEmptyBlocks(null, interval); const poolsStats: PoolStats[] = []; let rank = 1; + let blockCount = 0; poolsInfo.forEach((poolInfo: PoolInfo) => { - const emptyBlocksCount = emptyBlocks.filter((emptyCount) => emptyCount.poolId === poolInfo.poolId); const poolStat: PoolStats = { poolId: poolInfo.poolId, // mysql row id name: poolInfo.name, link: poolInfo.link, blockCount: poolInfo.blockCount, rank: rank++, - emptyBlocks: emptyBlocksCount.length > 0 ? emptyBlocksCount[0]['count'] : 0, + emptyBlocks: poolInfo.emptyBlocks, slug: poolInfo.slug, avgMatchRate: poolInfo.avgMatchRate !== null ? Math.round(100 * poolInfo.avgMatchRate) / 100 : null, avgFeeDelta: poolInfo.avgFeeDelta, poolUniqueId: poolInfo.poolUniqueId }; poolsStats.push(poolStat); + blockCount += poolInfo.blockCount; }); poolsStatistics['pools'] = poolsStats; - - const blockCount: number = await BlocksRepository.$blockCount(null, interval); poolsStatistics['blockCount'] = blockCount; const totalBlock24h: number = await BlocksRepository.$blockCount(null, '24h'); @@ -151,6 +149,8 @@ class Mining { poolsStatistics['lastEstimatedHashrate1w'] = await bitcoinClient.getNetworkHashPs(totalBlock1w); } catch (e) { poolsStatistics['lastEstimatedHashrate'] = 0; + poolsStatistics['lastEstimatedHashrate3d'] = 0; + poolsStatistics['lastEstimatedHashrate1w'] = 0; logger.debug('Bitcoin Core is not available, using zeroed value for current hashrate', logger.tags.mining); } @@ -175,8 +175,8 @@ class Mining { const blockCount1w: number = await BlocksRepository.$blockCount(pool.id, '1w'); const totalBlock1w: number = await BlocksRepository.$blockCount(null, '1w'); - const avgHealth = await BlocksRepository.$getAvgBlockHealthPerPoolId(pool.id); - const totalReward = await BlocksRepository.$getTotalRewardForPoolId(pool.id); + const avgHealth = await BlocksRepository.$getAvgBlockHealthPerPoolId(pool.id); + const totalReward = await BlocksRepository.$getTotalRewardForPoolId(pool.id); let currentEstimatedHashrate = 0; try { @@ -218,7 +218,7 @@ class Mining { const now = new Date(); // Run only if: - // * this.lastWeeklyHashrateIndexingDate is set to null (node backend restart, reorg) + // * this.lastWeeklyHashrateIndexingDate is set to null (node backend restart, reorg, or re-indexing was requested after mining pools update) // * we started a new week (around Monday midnight) const runIndexing = this.lastWeeklyHashrateIndexingDate === null || now.getUTCDay() === 1 && this.lastWeeklyHashrateIndexingDate !== now.getUTCDate(); @@ -235,7 +235,7 @@ class Mining { const indexedTimestamp = await HashratesRepository.$getWeeklyHashrateTimestamps(); const hashrates: any[] = []; - + const lastMonday = new Date(now.setDate(now.getDate() - (now.getDay() + 6) % 7)); const lastMondayMidnight = this.getDateMidnight(lastMonday); let toTimestamp = lastMondayMidnight.getTime(); @@ -326,6 +326,7 @@ class Mining { /** * Generate daily hashrate data + * @asyncUnsafe */ public async $generateNetworkHashrateHistory(): Promise { // If a re-index was requested, truncate first @@ -333,6 +334,7 @@ class Mining { logger.notice(`hashrates will now be re-indexed`); await database.query(`TRUNCATE hashrates`); this.lastHashrateIndexingDate = 0; + this.lastWeeklyHashrateIndexingDate = null; this.reindexHashrateRequested = false; } @@ -439,6 +441,7 @@ class Mining { /** * Index difficulty adjustments + * @asyncUnsafe */ public async $indexDifficultyAdjustments(): Promise { // If a re-index was requested, truncate first @@ -528,6 +531,8 @@ class Mining { /** * Create a link between blocks and the latest price at when they were mined + * + * @asyncSafe */ public async $indexBlockPrices(): Promise { if (this.blocksPriceIndexingRunning === true) { @@ -537,7 +542,7 @@ class Mining { let totalInserted = 0; try { - const prices: any[] = await PricesRepository.$getPricesTimesAndId(); + const prices: any[] = await PricesRepository.$getPricesTimesAndId(); const blocksWithoutPrices: any[] = await BlocksRepository.$getBlocksWithoutPrice(); const blocksPrices: BlockPrice[] = []; @@ -598,6 +603,8 @@ class Mining { /** * Index core coinstatsindex + * + * @asyncUnsafe */ public async $indexCoinStatsIndex(): Promise { let timer = new Date().getTime() / 1000; @@ -609,11 +616,11 @@ class Mining { while (currentBlockHeight > 0) { const indexedBlocks = await BlocksRepository.$getBlocksMissingCoinStatsIndex( currentBlockHeight, currentBlockHeight - 10000); - + for (const block of indexedBlocks) { const txoutset = await bitcoinClient.getTxoutSetinfo('none', block.height); await BlocksRepository.$updateCoinStatsIndexData(block.hash, txoutset.txouts, - Math.round(txoutset.block_info.prevout_spent * 100000000)); + Math.round(txoutset.block_info.prevout_spent * 100000000)); ++totalIndexed; const elapsedSeconds = Math.max(1, new Date().getTime() / 1000 - timer); @@ -635,6 +642,7 @@ class Mining { /** * List existing mining pools + * @asyncUnsafe */ public async $listPools(): Promise<{name: string, slug: string, unique_id: number}[] | null> { const [rows] = await database.query(` @@ -688,7 +696,7 @@ class Mining { default: return 1 * scale; } } - + // Finds the oldest block in a consecutive chain back from the tip // assumes `blocks` is sorted in ascending height order @@ -701,6 +709,7 @@ class Mining { return blocks[0]; } + /** @asyncUnsafe */ private async getGenesisData(): Promise<{timestamp: number, bits: number, difficulty: number}> { if (this.genesisData == null) { const genesisBlock: IEsploraApi.Block = await bitcoinApi.$getBlock(await bitcoinApi.$getBlockHash(0)); diff --git a/backend/src/api/pools-parser.ts b/backend/src/api/pools-parser.ts index 0bcf90bc9..92c8e919d 100644 --- a/backend/src/api/pools-parser.ts +++ b/backend/src/api/pools-parser.ts @@ -33,6 +33,7 @@ class PoolsParser { /** * Populate our db with updated mining pool definition * @param pools + * @asyncUnsafe */ public async migratePoolsJson(): Promise { // We also need to wipe the backend cache to make sure we don't serve blocks with @@ -126,8 +127,8 @@ class PoolsParser { block.extras.pool = reindexedBlock.extras.pool; } // update persistent cache with the reindexed data - diskCache.$saveCacheToDisk(); - redisCache.$updateBlocks(blocks.getBlocks()); + void diskCache.$saveCacheToDisk(); + void redisCache.$updateBlocks(blocks.getBlocks()); } } @@ -159,6 +160,7 @@ class PoolsParser { /** * Manually add the 'unknown pool' + * @asyncSafe */ public async $insertUnknownPool(): Promise { if (!config.DATABASE.ENABLED) { @@ -190,12 +192,13 @@ class PoolsParser { * re-index pool assignment for blocks previously associated with pool * * @param pool local id of existing pool to reindex + * @asyncUnsafe */ private async $reindexBlocksForPool(poolId: number): Promise { let firstKnownBlockPool = 130635; // https://mempool.space/block/0000000000000a067d94ff753eec72830f1205ad3a4c216a08a80c832e551a52 if (config.MEMPOOL.NETWORK === 'testnet') { firstKnownBlockPool = 21106; // https://mempool.space/testnet/block/0000000070b701a5b6a1b965f6a38e0472e70b2bb31b973e4638dec400877581 - } else if (['signet', 'testnet4'].includes(config.MEMPOOL.NETWORK)) { + } else if (['signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) { firstKnownBlockPool = 0; } diff --git a/backend/src/api/prices/prices.routes.ts b/backend/src/api/prices/prices.routes.ts index e395fb44b..cdf435f32 100644 --- a/backend/src/api/prices/prices.routes.ts +++ b/backend/src/api/prices/prices.routes.ts @@ -1,14 +1,11 @@ import { Application, Request, Response } from 'express'; import config from '../../config'; import pricesUpdater from '../../tasks/price-updater'; -import logger from '../../logger'; -import PricesRepository from '../../repositories/PricesRepository'; class PricesRoutes { public initRoutes(app: Application): void { app .get(config.MEMPOOL.API_URL_PREFIX + 'prices', this.$getCurrentPrices.bind(this)) - .get(config.MEMPOOL.API_URL_PREFIX + 'internal/usd-price-history', this.$getAllPrices.bind(this)) ; } @@ -19,23 +16,6 @@ class PricesRoutes { res.json(pricesUpdater.getLatestPrices()); } - - private async $getAllPrices(req: Request, res: Response): Promise { - res.header('Pragma', 'public'); - res.header('Cache-control', 'public'); - res.setHeader('Expires', new Date(Date.now() + 360_0000 / config.MEMPOOL.PRICE_UPDATES_PER_HOUR).toUTCString()); - - try { - const usdPriceHistory = await PricesRepository.$getPricesTimesAndId(); - const responseData = usdPriceHistory.map(p => { - return { time: p.time, USD: p.USD }; - }); - res.status(200).json(responseData); - } catch (e: any) { - logger.err(`Exception ${e} in PricesRoutes::$getAllPrices. Code: ${e.code}. Message: ${e.message}`); - res.status(403).send(); - } - } } export default new PricesRoutes(); diff --git a/backend/src/api/rbf-cache.ts b/backend/src/api/rbf-cache.ts index edd22a582..10db094ab 100644 --- a/backend/src/api/rbf-cache.ts +++ b/backend/src/api/rbf-cache.ts @@ -1,10 +1,10 @@ -import config from "../config"; -import logger from "../logger"; -import { MempoolTransactionExtended, TransactionStripped } from "../mempool.interfaces"; +import config from '../config'; +import logger from '../logger'; +import { MempoolTransactionExtended, TransactionStripped } from '../mempool.interfaces'; import bitcoinApi from './bitcoin/bitcoin-api-factory'; -import { IEsploraApi } from "./bitcoin/esplora-api.interface"; -import { Common } from "./common"; -import redisCache from "./redis-cache"; +import { IEsploraApi } from './bitcoin/esplora-api.interface'; +import { Common } from './common'; +import redisCache from './redis-cache'; export interface RbfTransaction extends TransactionStripped { rbf?: boolean; @@ -407,6 +407,7 @@ class RbfCache { }; } + /** @asyncSafe */ public async load({ txs, trees, expiring, mempool, spendMap }): Promise { try { txs.forEach(txEntry => { diff --git a/backend/src/api/redis-cache.ts b/backend/src/api/redis-cache.ts index 1caade15b..5858603c7 100644 --- a/backend/src/api/redis-cache.ts +++ b/backend/src/api/redis-cache.ts @@ -23,11 +23,14 @@ class RedisCache { private pauseFlush: boolean = false; private cacheQueue: MempoolTransactionExtended[] = []; - private removeQueue: string[] = []; + private removeQueue = new Set(); + private removeQueueFlushInProgress: boolean = false; private rbfCacheQueue: { type: string, txid: string, value: any }[] = []; private rbfRemoveQueue: { type: string, txid: string }[] = []; private txFlushLimit: number = 10000; private ignoreBlocksCache = false; + private reconciliationInProgress: boolean = false; + private reconciliationCursor: string = '0'; constructor() { if (config.REDIS.ENABLED) { @@ -37,11 +40,13 @@ class RedisCache { }, database: NetworkDB[config.MEMPOOL.NETWORK], }; - this.$ensureConnected(); - setInterval(() => { this.$ensureConnected(); }, 10000); + void this.$ensureConnected(); + setInterval(() => { void this.$ensureConnected(); }, 10000); + setInterval(() => { void this.$reconcileMempoolTransactions(); }, 30000); } } + /** @asyncSafe */ private async $ensureConnected(): Promise { if (!this.connected && config.REDIS.ENABLED) { try { @@ -91,10 +96,11 @@ class RedisCache { private async $onConnected(): Promise { await this.$flushTransactions(); - await this.$removeTransactions([]); + await this.$removeTransactions(); await this.$flushRbfQueues(); } + /** @asyncSafe */ async $updateBlocks(blocks: BlockExtended[]): Promise { if (!config.REDIS.ENABLED) { return; @@ -127,10 +133,12 @@ class RedisCache { } } + /** @asyncSafe */ async $addTransaction(tx: MempoolTransactionExtended): Promise { if (!config.REDIS.ENABLED) { return; } + this.removeQueue.delete(tx.txid); this.cacheQueue.push(tx); if (this.cacheQueue.length >= this.txFlushLimit) { if (!this.pauseFlush) { @@ -139,6 +147,7 @@ class RedisCache { } } + /** @asyncSafe */ async $flushTransactions(): Promise { if (!config.REDIS.ENABLED) { return; @@ -178,34 +187,92 @@ class RedisCache { } } - async $removeTransactions(transactions: string[]): Promise { + queueTransactionsForRemoval(transactions: string[]): void { if (!config.REDIS.ENABLED) { return; } - const toRemove = this.removeQueue.concat(transactions); - this.removeQueue = []; - let failed: string[] = []; - let numRemoved = 0; - if (this.connected) { + + for (const txid of transactions) { + this.removeQueue.add(txid); + } + } + + /** @asyncSafe */ + async $removeTransactions(transactions: string[] = []): Promise { + if (!config.REDIS.ENABLED) { + return; + } + for (const txid of transactions) { + this.removeQueue.add(txid); + } + await this.$flushQueuedMempoolTxRemovals(); + } + + // incrementally reconcile the redis cache with the in-memory mempool + // by scanning for cached txs no longer in the mempool and marking for deletion + // each invocation scans 1000 keys, cursor loops back to the start after completing a full scan + /** @asyncSafe */ + private async $reconcileMempoolTransactions(): Promise { + if (!config.REDIS.ENABLED || !this.connected || this.reconciliationInProgress || !memPool.isInSync()) { + return; + } + + this.reconciliationInProgress = true; + try { + const result = await this.client.scan(this.reconciliationCursor, { + MATCH: 'mempool:tx:*', + COUNT: 1000 + }); + const mempool = memPool.getMempool(); + let staleCount = 0; + this.reconciliationCursor = result.cursor.toString(); + + for (const key of result.keys) { + const txid = key.slice('mempool:tx:'.length); + if (!mempool[txid]) { + this.removeQueue.add(txid); + staleCount++; + } + } + + if (staleCount) { + logger.debug(`Removing ${staleCount} stale transactions from the redis cache`); + void this.$removeTransactions(); + } + } catch (e) { + logger.warn(`Failed to reconcile Redis mempool cache: ${e instanceof Error ? e.message : e}`); + } finally { + this.reconciliationInProgress = false; + } + } + + /** @asyncSafe */ + private async $flushQueuedMempoolTxRemovals(): Promise { + if (!this.connected || !this.removeQueue.size || this.removeQueueFlushInProgress) { + return; + } + + this.removeQueueFlushInProgress = true; + const toRemove = Array.from(this.removeQueue); + this.removeQueue.clear(); + try { const sliceLength = config.REDIS.BATCH_QUERY_BASE_SIZE; for (let i = 0; i < Math.ceil(toRemove.length / sliceLength); i++) { const slice = toRemove.slice(i * sliceLength, (i + 1) * sliceLength); try { await this.client.unlink(slice.map(txid => `mempool:tx:${txid}`)); - numRemoved+= sliceLength; logger.debug(`Deleted ${slice.length} transactions from the Redis cache`); } catch (e) { logger.warn(`Failed to remove ${slice.length} transactions from Redis cache: ${e instanceof Error ? e.message : e}`); - failed = failed.concat(slice); + this.queueTransactionsForRemoval(slice); } } - // concat instead of replace, in case more txs have been added in the meantime - this.removeQueue = this.removeQueue.concat(failed); - } else { - this.removeQueue = this.removeQueue.concat(toRemove); + } finally { + this.removeQueueFlushInProgress = false; } } + /** @asyncSafe */ async $setRbfEntry(type: string, txid: string, value: any): Promise { if (!config.REDIS.ENABLED) { return; @@ -222,6 +289,7 @@ class RedisCache { } } + /** @asyncSafe */ async $removeRbfEntry(type: string, txid: string): Promise { if (!config.REDIS.ENABLED) { return; @@ -238,6 +306,7 @@ class RedisCache { } } + /** @asyncSafe */ private async $flushRbfQueues(): Promise { if (!config.REDIS.ENABLED) { return; @@ -263,6 +332,7 @@ class RedisCache { } } + /** @asyncSafe */ async $getBlocks(): Promise { if (!config.REDIS.ENABLED) { return []; @@ -280,6 +350,7 @@ class RedisCache { } } + /** @asyncSafe */ async $getBlockSummaries(): Promise { if (!config.REDIS.ENABLED) { return []; @@ -297,7 +368,8 @@ class RedisCache { } } - async $getMempool(): Promise<{ [txid: string]: MempoolTransactionExtended }> { + /** @asyncSafe */ + async $getMempool(validTxids?: Set): Promise<{ [txid: string]: MempoolTransactionExtended }> { if (!config.REDIS.ENABLED) { return {}; } @@ -308,7 +380,9 @@ class RedisCache { const start = Date.now(); const mempool = {}; try { - const mempoolList = await this.scanKeys('mempool:tx:*'); + const mempoolList = validTxids?.size + ? await this.loadKeys('mempool:tx:*', Array.from(validTxids)) + : await this.scanKeys('mempool:tx:*'); for (const tx of mempoolList) { mempool[tx.key] = tx.value; } @@ -320,6 +394,7 @@ class RedisCache { return {}; } + /** @asyncSafe */ async $getRbfEntries(type: string): Promise { if (!config.REDIS.ENABLED) { return []; @@ -337,14 +412,15 @@ class RedisCache { } } - async $loadCache(): Promise { + /** @asyncUnsafe */ + async $loadCache(validTxids?: Set): Promise { if (!config.REDIS.ENABLED) { return; } logger.info('Restoring mempool and blocks data from Redis cache'); // Load mempool - const loadedMempool = await this.$getMempool(); + const loadedMempool = await this.$getMempool(validTxids); this.inflateLoadedTxs(loadedMempool); // Load rbf data const rbfTxs = await this.$getRbfEntries('tx'); @@ -385,12 +461,14 @@ class RedisCache { } } + /** @asyncUnsafe */ private async scanKeys(pattern): Promise<{ key: string, value: T }[]> { logger.info(`loading Redis entries for ${pattern}`); let keys: string[] = []; const result: { key: string, value: T }[] = []; const patternLength = pattern.length - 1; let count = 0; + /** @asyncUnsafe */ const processValues = async (keys): Promise => { const values = await this.client.MGET(keys); for (let i = 0; i < values.length; i++) { @@ -417,6 +495,29 @@ class RedisCache { return result; } + /** @asyncUnsafe */ + private async loadKeys(pattern, keys: string[]): Promise<{ key: string, value: T }[]> { + const prefix = pattern.slice(0, -1); + const result: { key: string, value: T }[] = []; + let count = 0; + /** @asyncUnsafe */ + const processValues = async (slice: string[]): Promise => { + const values = await this.client.MGET(slice.map(key => `${prefix}${key}`)); + for (let i = 0; i < values.length; i++) { + if (values[i]) { + result.push({ key: slice[i], value: JSON.parse(values[i]) }); + count++; + } + } + logger.info(`loaded ${count} entries from Redis cache`); + }; + for (let i = 0; i < Math.ceil(keys.length / 10000); i++) { + const slice = keys.slice(i * 10000, (i + 1) * 10000); + await processValues(slice); + } + return result; + } + public setIgnoreBlocksCache(): void { this.ignoreBlocksCache = true; } diff --git a/backend/src/api/services/acceleration.ts b/backend/src/api/services/acceleration.ts index 053da6e82..02d3482f0 100644 --- a/backend/src/api/services/acceleration.ts +++ b/backend/src/api/services/acceleration.ts @@ -5,6 +5,7 @@ import { BlockExtended } from '../../mempool.interfaces'; import axios from 'axios'; import mempool from '../mempool'; import websocketHandler from '../websocket-handler'; +import { Common } from '../common'; type MyAccelerationStatus = 'requested' | 'accelerating' | 'done'; @@ -74,6 +75,7 @@ class AccelerationApi { this.forcePoll = true; } + /** @asyncSafe */ private async $fetchAccelerations(): Promise { try { const response = await axios.get(this.apiPath, { responseType: 'json', timeout: 10000 }); @@ -238,6 +240,7 @@ class AccelerationApi { } } + /** @asyncSafe */ public async connectWebsocket(): Promise { if (this.startedWebsocketLoop) { return; @@ -314,7 +317,7 @@ class AccelerationApi { } } } - await new Promise(resolve => setTimeout(resolve, 5000)); + await Common.sleep$(5000); } } } diff --git a/backend/src/api/services/stratum.ts b/backend/src/api/services/stratum.ts index a8ee64106..57c5d3f8d 100644 --- a/backend/src/api/services/stratum.ts +++ b/backend/src/api/services/stratum.ts @@ -2,6 +2,7 @@ import { WebSocket } from 'ws'; import logger from '../../logger'; import config from '../../config'; import websocketHandler from '../websocket-handler'; +import { Common } from '../common'; export interface StratumJob { pool: number; @@ -58,6 +59,7 @@ class StratumApi { } } + /** @asyncSafe */ public async connectWebsocket(): Promise { if (!config.STRATUM.ENABLED) { return; @@ -97,7 +99,7 @@ class StratumApi { } }); } - await new Promise(resolve => setTimeout(resolve, 5000)); + await Common.sleep$(5000); } } } diff --git a/backend/src/api/services/wallets.ts b/backend/src/api/services/wallets.ts index e4578f5dc..fb28e18d8 100644 --- a/backend/src/api/services/wallets.ts +++ b/backend/src/api/services/wallets.ts @@ -8,7 +8,6 @@ import { promises as fsPromises } from 'fs'; interface WalletAddress { address: string; - active: boolean; stats: { funded_txo_count: number; funded_txo_sum: number; @@ -56,10 +55,11 @@ class WalletApi { // Load cache on startup if (config.WALLETS.ENABLED) { - this.$loadCache(); + void this.$loadCache(); } } + /** @asyncSafe */ private async $loadCache(): Promise { try { const cacheData = await fsPromises.readFile(WalletApi.FILE_NAME, 'utf8'); @@ -148,6 +148,7 @@ class WalletApi { } // resync wallet addresses from the services backend + /** @asyncSafe */ async $syncWallets(): Promise { if (!config.WALLETS.ENABLED || this.syncing) { return; @@ -242,15 +243,18 @@ class WalletApi { // resync address transactions from esplora async $syncWalletAddress(wallet: Wallet, address: WalletAddress): Promise { - // fetch full transaction data if the address is new or still active and hasn't been synced in the last hour - const refreshTransactions = !wallet.addresses[address.address] || (address.active && (Date.now() - wallet.addresses[address.address].lastSync) > 60 * 60 * 1000); + if (address.address === 'private') { + // skip pseudo-address for private balances + return; + } + // fetch full transaction data if the address is new or hasn't been synced in the last hour + const refreshTransactions = !wallet.addresses[address.address] || (Date.now() - wallet.addresses[address.address].lastSync) > 60 * 60 * 1000; if (refreshTransactions) { try { const summary = await bitcoinApi.$getAddressTransactionSummary(address.address); const addressInfo = await bitcoinApi.$getAddress(address.address); const walletAddress: WalletAddress = { address: address.address, - active: address.active, transactions: summary, stats: addressInfo.chain_stats, lastSync: Date.now(), @@ -320,7 +324,6 @@ function convertBalancesToWalletAddress(wallet: string, balances: { balance: num const sortedBalances = balances.sort((a, b) => a.time - b.time); const walletAddress: WalletAddress = { address: 'private', - active: false, stats: { funded_txo_count: 0, funded_txo_sum: sortedBalances[sortedBalances.length - 1].balance, diff --git a/backend/src/api/statistics/statistics-api.ts b/backend/src/api/statistics/statistics-api.ts index fa13b60b9..5a3222e82 100644 --- a/backend/src/api/statistics/statistics-api.ts +++ b/backend/src/api/statistics/statistics-api.ts @@ -514,7 +514,7 @@ class StatisticsApi { vsize_1600: completeVsizes[36], vsize_1800: completeVsizes[37], vsize_2000: completeVsizes[38], - } + }; }); } } diff --git a/backend/src/api/statistics/statistics.ts b/backend/src/api/statistics/statistics.ts index 62dff3d66..d6ac7a543 100644 --- a/backend/src/api/statistics/statistics.ts +++ b/backend/src/api/statistics/statistics.ts @@ -22,13 +22,14 @@ class Statistics { const difference = nextInterval.getTime() - now.getTime(); setTimeout(() => { - this.runStatistics(); + void this.runStatistics(); this.intervalTimer = setInterval(() => { - this.runStatistics(true); + void this.runStatistics(true); }, 1 * 60 * 1000); }, difference); } + /** @asyncSafe */ public async runStatistics(skipIfRecent = false): Promise { if (!memPool.isInSync()) { return; diff --git a/backend/src/api/transaction-utils.ts b/backend/src/api/transaction-utils.ts index dc1012aad..caf589708 100644 --- a/backend/src/api/transaction-utils.ts +++ b/backend/src/api/transaction-utils.ts @@ -47,6 +47,7 @@ class TransactionUtils { * @param addPrevouts * @param lazyPrevouts * @param forceCore - See https://github.com/mempool/mempool/issues/2904 + * @asyncUnsafe */ public async $getTransactionExtended(txId: string, addPrevouts = false, lazyPrevouts = false, forceCore = false, addMempoolData = false): Promise { let transaction: IEsploraApi.Transaction; @@ -69,10 +70,12 @@ class TransactionUtils { } } + /** @asyncUnsafe */ public async $getMempoolTransactionExtended(txId: string, addPrevouts = false, lazyPrevouts = false, forceCore = false): Promise { return (await this.$getTransactionExtended(txId, addPrevouts, lazyPrevouts, forceCore, true)) as MempoolTransactionExtended; } + /** @asyncUnsafe */ public async $getMempoolTransactionsExtended(txids: string[], addPrevouts = false, lazyPrevouts = false, forceCore = false): Promise { if (forceCore || config.MEMPOOL.BACKEND !== 'esplora') { const limiter = pLimit(8); // Run 8 requests at a time @@ -116,7 +119,7 @@ class TransactionUtils { public extendMempoolTransaction(transaction: IEsploraApi.Transaction): MempoolTransactionExtended { const vsize = Math.ceil(transaction.weight / 4); const fractionalVsize = (transaction.weight / 4); - let sigops = Common.isLiquid() ? 0 : (transaction.sigops != null ? transaction.sigops : this.countSigops(transaction)); + const sigops = Common.isLiquid() ? 0 : (transaction.sigops != null ? transaction.sigops : this.countSigops(transaction)); // https://github.com/bitcoin/bitcoin/blob/e9262ea32a6e1d364fb7974844fadc36f931f8c6/src/policy/policy.cpp#L295-L298 const adjustedVsize = Math.max(fractionalVsize, sigops * 5); // adjusted vsize = Max(weight, sigops * bytes_per_sigop) / witness_scale_factor const feePerVbytes = (transaction.fee || 0) / fractionalVsize; @@ -253,13 +256,14 @@ class TransactionUtils { // returns the most significant 4 bytes of the txid as an integer public txidToOrdering(txid: string): number { - return parseInt( - txid.substr(62, 2) + - txid.substr(60, 2) + - txid.substr(58, 2) + - txid.substr(56, 2), - 16 - ); + // Parse last 4 bytes of txid as little-endian uint32, without string allocation + let result = 0; + for (let i = 62; i >= 56; i -= 2) { + const hi = txid.charCodeAt(i); + const lo = txid.charCodeAt(i + 1); + result = result * 256 + (hi < 58 ? hi - 48 : hi - 87) * 16 + (lo < 58 ? lo - 48 : lo - 87); + } + return result; } public addInnerScriptsToVin(vin: IEsploraApi.Vin): void { @@ -267,7 +271,7 @@ class TransactionUtils { return; } - if (vin.prevout.scriptpubkey_type === 'p2sh') { + if (vin.prevout.scriptpubkey_type === 'p2sh' && vin.scriptsig_asm?.length) { const redeemScript = vin.scriptsig_asm.split(' ').reverse()[0]; vin.inner_redeemscript_asm = this.convertScriptSigAsm(redeemScript); if (vin.witness && vin.witness.length > 2) { @@ -300,15 +304,15 @@ class TransactionUtils { if (op >= 0x01 && op <= 0x4e) { i++; let push: number; - if (op === 0x4c) { + if (op === 0x4c && buf.length > i) { push = buf.readUInt8(i); b.push('OP_PUSHDATA1'); i += 1; - } else if (op === 0x4d) { + } else if (op === 0x4d && buf.length > i + 1) { push = buf.readUInt16LE(i); b.push('OP_PUSHDATA2'); i += 2; - } else if (op === 0x4e) { + } else if (op === 0x4e && buf.length > i + 3) { push = buf.readUInt32LE(i); b.push('OP_PUSHDATA4'); i += 4; @@ -317,13 +321,15 @@ class TransactionUtils { b.push('OP_PUSHBYTES_' + push); } - const data = buf.slice(i, i + push); + if (i >= buf.length) { + break; + } + const data = buf.subarray(i, Math.min(i + push, buf.length)); + b.push(data.toString('hex')); + i += data.length; if (data.length !== push) { break; } - - b.push(data.toString('hex')); - i += data.length; } else { if (op === 0x00) { b.push('OP_0'); @@ -363,7 +369,7 @@ class TransactionUtils { * the script item if it is a script spend. */ public witnessToP2TRScript(witness: string[]): string | null { - if (witness.length < 2) return null; + if (witness.length < 2) {return null;} // Note: see BIP341 for parsing details of witness stack // If there are at least two witness elements, and the first byte of the @@ -373,7 +379,7 @@ class TransactionUtils { // If there are at least two witness elements left, script path spending is used. // Call the second-to-last stack element s, the script. // (Note: this phrasing from BIP341 assumes we've *removed* the annex from the stack) - if (hasAnnex && witness.length < 3) return null; + if (hasAnnex && witness.length < 3) {return null;} const positionOfScript = hasAnnex ? witness.length - 3 : witness.length - 2; return witness[positionOfScript]; } @@ -480,7 +486,7 @@ class TransactionUtils { return 'unknown'; } } - + } export default new TransactionUtils(); diff --git a/backend/src/api/tx-selection-worker.ts b/backend/src/api/tx-selection-worker.ts index 8ac7328fe..98dd42909 100644 --- a/backend/src/api/tx-selection-worker.ts +++ b/backend/src/api/tx-selection-worker.ts @@ -18,7 +18,7 @@ if (parentPort) { mempool.delete(uid); }); } - + const { blocks, rates, clusters } = makeBlockTemplates(mempool); // return the result to main thread. @@ -38,7 +38,7 @@ function makeBlockTemplates(mempool: Map) const auditPool: Map = new Map(); const mempoolArray: AuditTransaction[] = []; const cpfpClusters: Map = new Map(); - + mempool.forEach(tx => { tx.dirty = false; // initializing everything up front helps V8 optimize property access later @@ -85,7 +85,7 @@ function makeBlockTemplates(mempool: Map) // (i.e. the package rooted in the transaction with the best ancestor score) const blocks: number[][] = []; let blockWeight = 4000; - let blockSigops = 0; + const blockSigops = 0; let transactions: AuditTransaction[] = []; const modified: PairingHeap = new PairingHeap((a, b): boolean => { if (a.score === b.score) { diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 57fd46730..17bc72890 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -3,7 +3,7 @@ import * as WebSocket from 'ws'; import { BlockExtended, TransactionExtended, MempoolTransactionExtended, WebsocketResponse, OptimizedStatistic, ILoadingIndicators, GbtCandidates, TxTrackingInfo, - MempoolDelta, MempoolDeltaTxids + MempoolDelta, MempoolDeltaTxids, CpfpInfo } from '../mempool.interfaces'; import blocks from './blocks'; import memPool from './mempool'; @@ -16,17 +16,12 @@ import transactionUtils from './transaction-utils'; import rbfCache, { ReplacementInfo } from './rbf-cache'; import difficultyAdjustment from './difficulty-adjustment'; import feeApi from './fee-api'; -import BlocksRepository from '../repositories/BlocksRepository'; -import BlocksAuditsRepository from '../repositories/BlocksAuditsRepository'; -import BlocksSummariesRepository from '../repositories/BlocksSummariesRepository'; -import Audit from './audit'; import priceUpdater from '../tasks/price-updater'; import { ApiPrice } from '../repositories/PricesRepository'; import { Acceleration } from './services/acceleration'; import accelerationApi from './services/acceleration'; import mempool from './mempool'; import statistics from './statistics/statistics'; -import accelerationRepository from '../repositories/AccelerationRepository'; import bitcoinApi from './bitcoin/bitcoin-api-factory'; import walletApi from './services/wallets'; @@ -37,7 +32,6 @@ interface AddressTransactions { } import bitcoinSecondClient from './bitcoin/bitcoin-second-client'; import { calculateMempoolTxCpfp } from './cpfp'; -import { getRecentFirstSeen } from '../utils/file-read'; import stratumApi, { StratumJob } from './services/stratum'; // valid 'want' subscriptions @@ -64,6 +58,12 @@ class WebsocketHandler { private accelerations: Record = {}; + private MAX_BUFFERED_AMOUNT = 10_000_000; + public MAX_MESSAGE_SIZE = 50_000; + private MAX_TRACKED_TXS = 100; + private MSG_RATE_LIMIT = 100; + private MSG_RATE_WINDOW = 10_000; + constructor() { } addWebsocketServer(wss: WebSocket.Server) { @@ -102,7 +102,7 @@ class WebsocketHandler { 'backendInfo': backendInfo.getBackendInfo(), 'loadingIndicators': loadingIndicators.getLoadingIndicators(), 'da': da?.previousTime ? da : undefined, - 'fees': feeApi.getRecommendedFee(), + 'fees': feeApi.getPreciseRecommendedFee(), }); } @@ -120,6 +120,7 @@ class WebsocketHandler { server.on('connection', (client: WebSocket, req) => { this.numConnected++; client['remoteAddress'] = req.headers['x-forwarded-for'] || req.socket?.remoteAddress || 'unknown'; + client['msgTimestamps'] = []; client.on('error', (e) => { logger.info(`websocket client error from ${client['remoteAddress']}: ` + (e instanceof Error ? e.message : e)); client.close(); @@ -127,9 +128,30 @@ class WebsocketHandler { client.on('close', () => { this.numDisconnected++; }); - client.on('message', async (message: string) => { + client.on('message', async (message) => { try { - const parsedMessage: WebsocketResponse = JSON.parse(message); + const msgLength = Buffer.isBuffer(message) ? message.byteLength + : message instanceof ArrayBuffer ? message.byteLength + : message.reduce((sum, buf) => sum + buf.byteLength, 0); + if (msgLength > this.MAX_MESSAGE_SIZE) { + logger.debug(`Dropping oversized websocket message from ${client['remoteAddress']}: ${msgLength} bytes`); + client.terminate(); + return; + } + + const now = Date.now(); + const timestamps: number[] = client['msgTimestamps']; + timestamps.push(now); + while (timestamps.length && timestamps[0] <= now - this.MSG_RATE_WINDOW) { + timestamps.shift(); + } + if (timestamps.length > this.MSG_RATE_LIMIT) { + logger.debug(`Rate limiting websocket client ${client['remoteAddress']}`); + client.close(); + return; + } + + const parsedMessage: WebsocketResponse = JSON.parse(message as any); const response = {}; const wantNow = {}; @@ -225,45 +247,60 @@ class WebsocketHandler { if (parsedMessage && parsedMessage['track-txs']) { const txids: string[] = []; if (Array.isArray(parsedMessage['track-txs'])) { + if (parsedMessage['track-txs'].length > this.MAX_TRACKED_TXS) { + response['track-txs-error'] = `"too many txids requested, this connection supports tracking a maximum of ${this.MAX_TRACKED_TXS} transactions"`; + this.send(client, this.serializeResponse(response)); + client['track-txs'] = null; + client.close(); + return; + } for (const txid of parsedMessage['track-txs']) { if (/^[a-fA-F0-9]{64}$/.test(txid)) { txids.push(txid); } } + } else { + response['track-txs-error'] = `"incorrect track-txs format"`; + this.send(client, this.serializeResponse(response)); + client['track-txs'] = null; + client.close(); + return; } const txs: { [txid: string]: TxTrackingInfo } = {}; for (const txid of txids) { - const txInfo: TxTrackingInfo = { - confirmed: true, - }; + const txInfo: TxTrackingInfo = {}; const rbfCacheTxid = rbfCache.getReplacedBy(txid); if (rbfCacheTxid) { txInfo.replacedBy = rbfCacheTxid; txInfo.confirmed = false; + txs[txid] = txInfo; } const tx = memPool.getMempool()[txid]; - if (tx && tx.position) { - txInfo.position = { - ...tx.position - }; - if (tx.acceleration) { - txInfo.accelerated = tx.acceleration; - } - } if (tx) { + if (tx.position) { + txInfo.position = { + ...tx.position + }; + if (tx.acceleration) { + txInfo.accelerated = tx.acceleration; + } + } txInfo.confirmed = false; + txs[txid] = txInfo; } - txs[txid] = txInfo; } if (txids.length) { client['track-txs'] = txids; + client['track-txs-updates'] = 0; } else { client['track-txs'] = null; + client['track-txs-updates'] = 0; } if (Object.keys(txs).length) { + client['track-txs-updates'] = (client['track-txs-updates'] || 0) + Object.keys(txs).length; response['tracked-txs'] = JSON.stringify(txs); } } @@ -288,10 +325,13 @@ class WebsocketHandler { if (Object.keys(addressMap).length > config.MEMPOOL.MAX_TRACKED_ADDRESSES) { response['track-addresses-error'] = `"too many addresses requested, this connection supports tracking a maximum of ${config.MEMPOOL.MAX_TRACKED_ADDRESSES} addresses"`; client['track-addresses'] = null; + client['track-addresses-updates'] = 0; } else if (Object.keys(addressMap).length > 0) { client['track-addresses'] = addressMap; + client['track-addresses-updates'] = 0; } else { client['track-addresses'] = null; + client['track-addresses-updates'] = 0; } } @@ -315,8 +355,10 @@ class WebsocketHandler { if (parsedMessage && parsedMessage['track-wallet']) { if (parsedMessage['track-wallet'] === 'stop') { client['track-wallet'] = null; - } else { + } else if (typeof parsedMessage['track-wallet'] === 'string' && walletApi.getWallets().includes(parsedMessage['track-wallet'])) { client['track-wallet'] = parsedMessage['track-wallet']; + } else { + client['track-wallet'] = null; } } @@ -329,8 +371,8 @@ class WebsocketHandler { } if (parsedMessage && parsedMessage['track-mempool-block'] !== undefined) { - if (Number.isInteger(parsedMessage['track-mempool-block']) && parsedMessage['track-mempool-block'] >= 0) { - const index = parsedMessage['track-mempool-block']; + const index = parsedMessage['track-mempool-block']; + if (Number.isInteger(index) && index >= 0 && index < config.MEMPOOL.MEMPOOL_BLOCKS_AMOUNT) { client['track-mempool-block'] = index; const mBlocksWithTransactions = mempoolBlocks.getMempoolBlocksWithTransactions(); response['projected-block-transactions'] = JSON.stringify({ @@ -381,14 +423,14 @@ class WebsocketHandler { if (!this.socketData['blocks']?.length) { return; } - client.send(this.serializedInitData); + this.send(client, this.serializedInitData); } if (parsedMessage.action === 'ping') { response['pong'] = JSON.stringify(true); } - if (parsedMessage['track-donation'] && parsedMessage['track-donation'].length === 22) { + if (typeof parsedMessage['track-donation'] === 'string' && parsedMessage['track-donation'].length === 22) { client['track-donation'] = parsedMessage['track-donation']; } @@ -404,18 +446,18 @@ class WebsocketHandler { delete client['track-mempool']; } - if (parsedMessage && parsedMessage['track-stratum'] != null) { - if (parsedMessage['track-stratum']) { + if (parsedMessage && parsedMessage['track-stratum'] !== undefined) { + if (parsedMessage['track-stratum'] === 'all' || typeof parsedMessage['track-stratum'] === 'number') { const sub = parsedMessage['track-stratum']; client['track-stratum'] = sub; - response['stratumJobs'] = this.socketData['stratumJobs']; + response['stratumJobs'] = JSON.stringify(stratumApi.getJobs()); } else { client['track-stratum'] = false; } } if (Object.keys(response).length) { - client.send(this.serializeResponse(response)); + this.send(client, this.serializeResponse(response)); } } catch (e) { logger.debug(`Error parsing websocket message from ${client['remoteAddress']}: ` + (e instanceof Error ? e.message : e)); @@ -438,7 +480,7 @@ class WebsocketHandler { return; } if (client['track-donation'] === id) { - client.send(JSON.stringify({ donationConfirmed: true })); + this.send(client, JSON.stringify({ donationConfirmed: true })); } }); } @@ -458,7 +500,7 @@ class WebsocketHandler { if (client.readyState !== WebSocket.OPEN) { return; } - client.send(response); + this.send(client, response); }); } } @@ -477,7 +519,7 @@ class WebsocketHandler { if (client.readyState !== WebSocket.OPEN) { return; } - client.send(response); + this.send(client, response); }); } } @@ -504,7 +546,7 @@ class WebsocketHandler { return; } - client.send(response); + this.send(client, response); }); } } @@ -537,7 +579,7 @@ class WebsocketHandler { if (client.readyState !== WebSocket.OPEN) { return; } - client.send(response); + this.send(client, response); }); } } catch (e) { @@ -575,7 +617,7 @@ class WebsocketHandler { } if (Object.keys(response).length) { - client.send(this.serializeResponse(response)); + this.send(client, this.serializeResponse(response)); } }); } @@ -609,7 +651,10 @@ class WebsocketHandler { removed = candidates?.removed || []; } - if (config.MEMPOOL.RUST_GBT) { + if (config.MEMPOOL.CLUSTER_MEMPOOL) { + const cmBlocks = mempool.clusterMempool?.getBlocks(config.MEMPOOL.MEMPOOL_BLOCKS_AMOUNT) ?? []; + mempoolBlocks.processClusterMempoolBlocks(cmBlocks, newMempool, mempool.getAccelerations()); + } else if (config.MEMPOOL.RUST_GBT) { await mempoolBlocks.$rustUpdateBlockTemplates(transactionIds, newMempool, added, removed, candidates, true); } else { await mempoolBlocks.$updateBlockTemplates(transactionIds, newMempool, added, removed, candidates, accelerationDelta, true, true); @@ -639,7 +684,7 @@ class WebsocketHandler { } memPool.removeFromSpendMap(deletedTransactions); memPool.addToSpendMap(newTransactions); - const recommendedFees = feeApi.getRecommendedFee(); + const recommendedFees = feeApi.getPreciseRecommendedFee(); const latestTransactions = memPool.getLatestTransactions(); @@ -738,6 +783,8 @@ class WebsocketHandler { removed: websocketAccelerationDelta.filter(txid => !accelerations[txid]), }; + const cpfpUpdatesSent = new Set(); + // TODO - Fix indentation after PR is merged for (const server of this.webSocketServers) { server.clients.forEach(async (client) => { @@ -815,6 +862,8 @@ class WebsocketHandler { } if (Object.keys(addressMap).length > 0) { + client['track-addresses-updates'] = + (client['track-addresses-updates'] || 0) + this.countAddressTransactions(addressMap); response['multi-address-transactions'] = JSON.stringify(addressMap); } } @@ -906,15 +955,23 @@ class WebsocketHandler { calculateMempoolTxCpfp(mempoolTx, newMempool); } if (mempoolTx.cpfpDirty) { - positionData['cpfp'] = { - ancestors: mempoolTx.ancestors, + const cpfp: CpfpInfo = { + ancestors: mempoolTx.ancestors || [], bestDescendant: mempoolTx.bestDescendant || null, - descendants: mempoolTx.descendants || null, - effectiveFeePerVsize: mempoolTx.effectiveFeePerVsize || null, + descendants: mempoolTx.descendants, + effectiveFeePerVsize: mempoolTx.effectiveFeePerVsize, sigops: mempoolTx.sigops, adjustedVsize: mempoolTx.adjustedVsize, acceleration: mempoolTx.acceleration, }; + if (config.MEMPOOL.CLUSTER_MEMPOOL && mempoolTx.clusterId != null) { + const cluster = mempool.clusterMempool?.getClusterForApi(mempoolTx.txid); + if (cluster) { + cpfp.cluster = cluster; + } + } + positionData['cpfp'] = cpfp; + cpfpUpdatesSent.add(trackTxid); } response['txPosition'] = JSON.stringify(positionData); } @@ -925,13 +982,16 @@ class WebsocketHandler { const txs: { [txid: string]: TxTrackingInfo } = {}; for (const txid of txids) { const txInfo: TxTrackingInfo = {}; + let txHasInfo = false; const outspends = outspendCache[txid]; if (outspends && Object.keys(outspends).length) { txInfo.utxoSpent = outspends; + txHasInfo = true; } const replacedBy = rbfChanges.map[txid] ? rbfCache.getReplacedBy(txid) : false; if (replacedBy) { txInfo.replacedBy = replacedBy; + txHasInfo = true; } const mempoolTx = newMempool[txid]; if (mempoolTx && mempoolTx.position) { @@ -949,16 +1009,27 @@ class WebsocketHandler { txInfo.cpfp = { ancestors: mempoolTx.ancestors, bestDescendant: mempoolTx.bestDescendant || null, - descendants: mempoolTx.descendants || null, - effectiveFeePerVsize: mempoolTx.effectiveFeePerVsize || null, + descendants: mempoolTx.descendants, + effectiveFeePerVsize: mempoolTx.effectiveFeePerVsize, sigops: mempoolTx.sigops, adjustedVsize: mempoolTx.adjustedVsize, }; + if (config.MEMPOOL.CLUSTER_MEMPOOL && mempoolTx.clusterId != null) { + const cluster = mempool.clusterMempool?.getClusterForApi(mempoolTx.txid); + if (cluster) { + (txInfo.cpfp as CpfpInfo).cluster = cluster; + } + } + cpfpUpdatesSent.add(txid); } + txHasInfo = true; + } + if (txHasInfo) { + txs[txid] = txInfo; } - txs[txid] = txInfo; } if (Object.keys(txs).length) { + client['track-txs-updates'] = (client['track-txs-updates'] || 0) + Object.keys(txs).length; response['tracked-txs'] = JSON.stringify(txs); } } @@ -997,147 +1068,42 @@ class WebsocketHandler { } if (Object.keys(response).length) { - client.send(this.serializeResponse(response)); + this.send(client, this.serializeResponse(response)); } }); } + + for (const txid of cpfpUpdatesSent) { + if (newMempool[txid]) { + newMempool[txid].cpfpDirty = false; + } + } } - - async handleNewBlock(block: BlockExtended, txIds: string[], transactions: MempoolTransactionExtended[]): Promise { + + /** @asyncSafe */ + async handleNewBlock( + block: BlockExtended, + txIds: string[], + transactions: MempoolTransactionExtended[], + rbfTransactions: { [txid: string]: { replaced: MempoolTransactionExtended[], replacedBy: TransactionExtended }} + ): Promise { if (!this.webSocketServers.length) { throw new Error('No WebSocket.Server have been set'); } - const blockTransactions = structuredClone(transactions); - this.printLogs(); - if (config.STATISTICS.ENABLED && config.DATABASE.ENABLED) { - await statistics.runStatistics(); - } const _memPool = memPool.getMempool(); - const candidateTxs = memPool.getMempoolCandidates(); - let candidates: GbtCandidates | undefined = (memPool.limitGBT && candidateTxs) ? { txs: candidateTxs, added: [], removed: [] } : undefined; - let transactionIds: string[] = (memPool.limitGBT) ? Object.keys(candidates?.txs || {}) : Object.keys(_memPool); - - if (config.DATABASE.ENABLED) { - const accelerations = Object.values(mempool.getAccelerations()); - await accelerationRepository.$indexAccelerationsForBlock(block, accelerations, structuredClone(transactions)); - } - - const rbfTransactions = Common.findMinedRbfTransactions(transactions, memPool.getSpendMap()); - memPool.handleRbfTransactions(rbfTransactions); - memPool.removeFromSpendMap(transactions); - - if (config.MEMPOOL.AUDIT && memPool.isInSync()) { - let projectedBlocks; - const auditMempool = _memPool; - const isAccelerated = accelerationApi.isAcceleratedBlock(block, Object.values(mempool.getAccelerations())); - - if (config.MEMPOOL.RUST_GBT) { - const added = memPool.limitGBT ? (candidates?.added || []) : []; - const removed = memPool.limitGBT ? (candidates?.removed || []) : []; - projectedBlocks = await mempoolBlocks.$rustUpdateBlockTemplates(transactionIds, auditMempool, added, removed, candidates, isAccelerated, block.extras.pool.id); - } else { - projectedBlocks = await mempoolBlocks.$makeBlockTemplates(transactionIds, auditMempool, candidates, false, isAccelerated, block.extras.pool.id); - } - - if (Common.indexingEnabled()) { - const { unseen, censored, added, prioritized, fresh, sigop, fullrbf, accelerated, score, similarity } = Audit.auditBlock(block.height, blockTransactions, projectedBlocks, auditMempool); - const matchRate = Math.round(score * 100 * 100) / 100; - - const stripped = projectedBlocks[0]?.transactions ? projectedBlocks[0].transactions : []; - - let totalFees = 0; - let totalWeight = 0; - for (const tx of stripped) { - totalFees += tx.fee; - totalWeight += (tx.vsize * 4); - } - - BlocksSummariesRepository.$saveTemplate({ - height: block.height, - template: { - id: block.id, - transactions: stripped, - }, - version: 1, - }); - - BlocksAuditsRepository.$saveAudit({ - version: 1, - time: block.timestamp, - height: block.height, - hash: block.id, - unseenTxs: unseen, - addedTxs: added, - prioritizedTxs: prioritized, - missingTxs: censored, - freshTxs: fresh, - sigopTxs: sigop, - fullrbfTxs: fullrbf, - acceleratedTxs: accelerated, - matchRate: matchRate, - expectedFees: totalFees, - expectedWeight: totalWeight, - }); - - if (block.extras) { - block.extras.matchRate = matchRate; - block.extras.expectedFees = totalFees; - block.extras.expectedWeight = totalWeight; - block.extras.similarity = similarity; - } - } - } else if (block.extras) { - const mBlocks = mempoolBlocks.getMempoolBlocksWithTransactions(); - if (mBlocks?.length && mBlocks[0].transactions) { - block.extras.similarity = Common.getSimilarity(mBlocks[0], transactions); - } - } - - if (config.CORE_RPC.DEBUG_LOG_PATH && block.extras) { - const firstSeen = getRecentFirstSeen(block.id); - if (firstSeen) { - if (config.DATABASE.ENABLED) { - BlocksRepository.$saveFirstSeenTime(block.id, firstSeen); - } - block.extras.firstSeen = firstSeen; - } - } - const confirmedTxids: { [txid: string]: boolean } = {}; - - // Update mempool to remove transactions included in the new block for (const txId of txIds) { - delete _memPool[txId]; - rbfCache.mined(txId); confirmedTxids[txId] = true; } - if (memPool.limitGBT) { - const minFeeMempool = memPool.limitGBT ? await bitcoinSecondClient.getRawMemPool() : null; - const minFeeTip = memPool.limitGBT ? await bitcoinSecondClient.getBlockCount() : -1; - candidates = memPool.getNextCandidates(minFeeMempool, minFeeTip, transactions); - transactionIds = Object.keys(candidates?.txs || {}); - } else { - candidates = undefined; - transactionIds = Object.keys(memPool.getMempool()); - } - - - if (config.MEMPOOL.RUST_GBT) { - const added = memPool.limitGBT ? (candidates?.added || []) : []; - const removed = memPool.limitGBT ? (candidates?.removed || []) : transactions; - await mempoolBlocks.$rustUpdateBlockTemplates(transactionIds, _memPool, added, removed, candidates, true); - } else { - await mempoolBlocks.$makeBlockTemplates(transactionIds, _memPool, candidates, true, true); - } const mBlocks = mempoolBlocks.getMempoolBlocks(); const mBlockDeltas = mempoolBlocks.getMempoolBlockDeltas(); const da = difficultyAdjustment.getDifficultyAdjustment(); - const fees = feeApi.getRecommendedFee(); + const fees = feeApi.getPreciseRecommendedFee(); const mempoolInfo = memPool.getMempoolInfo(); // pre-compute address transactions @@ -1265,6 +1231,7 @@ class WebsocketHandler { } } if (Object.keys(txs).length) { + client['track-txs-updates'] = (client['track-txs-updates'] || 0) + Object.keys(txs).length; response['tracked-txs'] = JSON.stringify(txs); } } @@ -1300,6 +1267,8 @@ class WebsocketHandler { } if (Object.keys(addressMap).length > 0) { + client['track-addresses-updates'] = + (client['track-addresses-updates'] || 0) + this.countAddressTransactions(addressMap); response['multi-address-transactions'] = JSON.stringify(addressMap); } } @@ -1393,26 +1362,20 @@ class WebsocketHandler { } if (Object.keys(response).length) { - client.send(this.serializeResponse(response)); + this.send(client, this.serializeResponse(response)); } }); } - - if (config.STATISTICS.ENABLED && config.DATABASE.ENABLED) { - await statistics.runStatistics(); - } } public handleNewStratumJob(job: StratumJob): void { - this.updateSocketDataFields({ 'stratumJobs': stratumApi.getJobs() }); - for (const server of this.webSocketServers) { server.clients.forEach((client) => { if (client.readyState !== WebSocket.OPEN) { return; } if (client['track-stratum'] && (client['track-stratum'] === 'all' || client['track-stratum'] === job.pool)) { - client.send(JSON.stringify({ + this.send(client, JSON.stringify({ 'stratumJob': job })); } @@ -1420,6 +1383,14 @@ class WebsocketHandler { } } + private send(client: WebSocket.WebSocket, data: string): void { + if (client.bufferedAmount > this.MAX_BUFFERED_AMOUNT) { + client.terminate(); + return; + } + client.send(data); + } + // takes a dictionary of JSON serialized values // and zips it together into a valid JSON object private serializeResponse(response): string { @@ -1485,6 +1456,7 @@ class WebsocketHandler { return addressCache; } + /** @asyncSafe */ private async getFullTransactions(transactions: MempoolTransactionExtended[]): Promise { for (let i = 0; i < transactions.length; i++) { try { @@ -1500,25 +1472,72 @@ class WebsocketHandler { if (this.webSocketServers.length) { let numTxSubs = 0; let numTxsSubs = 0; + let numAddressSubs = 0; + let numAddressesSubs = 0; let numProjectedSubs = 0; let numRbfSubs = 0; + let trackedTxsTotal = 0; + let trackedAddressesTotal = 0; + let trackedTxsMax = 0; + let trackedAddressesMax = 0; + let trackTxsTrackedTotal = 0; + let trackTxsTrackedMax = 0; + let trackAddressesTrackedTotal = 0; + let trackAddressesTrackedMax = 0; + let trackTxsUpdatesTotal = 0; + let trackTxsUpdatesMax = 0; + let trackAddressesUpdatesTotal = 0; + let trackAddressesUpdatesMax = 0; - // TODO - Fix indentation after PR is merged for (const server of this.webSocketServers) { - server.clients.forEach((client) => { - if (client['track-tx']) { - numTxSubs++; - } - if (client['track-txs']) { - numTxsSubs++; - } - if (client['track-mempool-block'] != null && client['track-mempool-block'] >= 0) { - numProjectedSubs++; - } - if (client['track-rbf']) { - numRbfSubs++; - } - }) + server.clients.forEach((client) => { + let trackedTxCount = 0; + let trackedAddressCount = 0; + + if (client['track-tx']) { + numTxSubs++; + trackedTxCount += 1; + } + if (client['track-txs']) { + numTxsSubs++; + trackedTxCount += client['track-txs'].length; + } + if (client['track-address']) { + numAddressSubs++; + trackedAddressCount += 1; + } + if (client['track-addresses']) { + numAddressesSubs++; + const addressCount = Object.keys(client['track-addresses']).length; + trackedAddressCount += addressCount; + trackAddressesTrackedTotal += addressCount; + trackAddressesTrackedMax = Math.max(trackAddressesTrackedMax, addressCount); + const updates = client['track-addresses-updates'] || 0; + trackAddressesUpdatesTotal += updates; + trackAddressesUpdatesMax = Math.max(trackAddressesUpdatesMax, updates); + client['track-addresses-updates'] = 0; + } + if (client['track-mempool-block'] != null && client['track-mempool-block'] >= 0) { + numProjectedSubs++; + } + if (client['track-rbf']) { + numRbfSubs++; + } + if (client['track-txs']) { + const txCount = client['track-txs'].length; + trackTxsTrackedTotal += txCount; + trackTxsTrackedMax = Math.max(trackTxsTrackedMax, txCount); + const updates = client['track-txs-updates'] || 0; + trackTxsUpdatesTotal += updates; + trackTxsUpdatesMax = Math.max(trackTxsUpdatesMax, updates); + client['track-txs-updates'] = 0; + } + + trackedTxsTotal += trackedTxCount; + trackedAddressesTotal += trackedAddressCount; + trackedTxsMax = Math.max(trackedTxsMax, trackedTxCount); + trackedAddressesMax = Math.max(trackedAddressesMax, trackedAddressCount); + }); } let count = 0; @@ -1527,12 +1546,33 @@ class WebsocketHandler { } const diff = count - this.numClients; this.numClients = count; - logger.debug(`${count} websocket clients | ${this.numConnected} connected | ${this.numDisconnected} disconnected | (${diff >= 0 ? '+' : ''}${diff})`); - logger.debug(`websocket subscriptions: track-tx: ${numTxSubs}, track-txs: ${numTxsSubs}, track-mempool-block: ${numProjectedSubs} track-rbf: ${numRbfSubs}`); + const trackedTxsAvg = count > 0 ? trackedTxsTotal / count : 0; + const trackedAddressesAvg = count > 0 ? trackedAddressesTotal / count : 0; + const trackTxsTrackedAvg = numTxsSubs > 0 ? trackTxsTrackedTotal / numTxsSubs : 0; + const trackAddressesTrackedAvg = + numAddressesSubs > 0 ? trackAddressesTrackedTotal / numAddressesSubs : 0; + const trackTxsUpdatesAvg = numTxsSubs > 0 ? trackTxsUpdatesTotal / numTxsSubs : 0; + const trackAddressesUpdatesAvg = + numAddressesSubs > 0 ? trackAddressesUpdatesTotal / numAddressesSubs : 0; + logger.debug( + `${count} websocket clients | ${this.numConnected} connected | ${this.numDisconnected} disconnected | (${diff >= 0 ? '+' : ''}${diff}) | tracked txs: total=${trackedTxsTotal}, avg=${trackedTxsAvg.toFixed(2)}, max=${trackedTxsMax} | tracked addresses: total=${trackedAddressesTotal}, avg=${trackedAddressesAvg.toFixed(2)}, max=${trackedAddressesMax} | ws-subscriptions: tx=${numTxSubs},txs=${numTxsSubs},address=${numAddressSubs},addresses=${numAddressesSubs},txs-tracked-avg=${trackTxsTrackedAvg.toFixed(2)},txs-tracked-max=${trackTxsTrackedMax},addresses-tracked-avg=${trackAddressesTrackedAvg.toFixed(2)},addresses-tracked-max=${trackAddressesTrackedMax},txs-updates-avg=${trackTxsUpdatesAvg.toFixed(2)},txs-updates-max=${trackTxsUpdatesMax},addresses-updates-avg=${trackAddressesUpdatesAvg.toFixed(2)},addresses-updates-max=${trackAddressesUpdatesMax}` + ); + logger.debug(`websocket subscriptions: track-tx: ${numTxSubs}, track-txs: ${numTxsSubs}, track-address: ${numAddressSubs}, track-addresses: ${numAddressesSubs}, track-mempool-block: ${numProjectedSubs} track-rbf: ${numRbfSubs}`); this.numConnected = 0; this.numDisconnected = 0; } } + + private countAddressTransactions(addressMap: { [address: string]: AddressTransactions }): number { + return Object.values(addressMap).reduce( + (total, transactions) => + total + + transactions.mempool.length + + transactions.confirmed.length + + transactions.removed.length, + 0, + ); + } } export default new WebsocketHandler(); diff --git a/backend/src/cluster-mempool/block-builder.ts b/backend/src/cluster-mempool/block-builder.ts new file mode 100644 index 000000000..3f6a4b687 --- /dev/null +++ b/backend/src/cluster-mempool/block-builder.ts @@ -0,0 +1,165 @@ +import { PairingHeap } from '../utils/pairing-heap'; +import { LinearizationChunk } from './linearize'; +import { MempoolTransactionExtended } from '../mempool.interfaces'; +import { Cluster } from './cluster-mempool'; + +export interface ProjectedBlock { + txids: string[]; + weight: number; + sigops: number; +} + +interface ChunkHeapEntry { + fee: number; + weight: number; + sigops: number; + equalFeeratePrefixWeight: number; + maxOrder: number; + clusterId: number; + chunkIndex: number; +} + +const BLOCK_WEIGHT_UNITS = 4_000_000; +const MAX_BLOCK_SIGOPS_COST = 80_000; +const COINBASE_RESERVED_WEIGHT = 8000; +const MAX_CONSECUTIVE_FAILURES = 1000; +const MAX_WASTED_WEIGHT = 4000; + +function chunkHeapHigherPriority(a: ChunkHeapEntry, b: ChunkHeapEntry): boolean { + const feerateDiff = a.fee * b.weight - b.fee * a.weight; + if (feerateDiff !== 0) { + return feerateDiff > 0; + } + if (a.equalFeeratePrefixWeight !== b.equalFeeratePrefixWeight) { + return a.equalFeeratePrefixWeight < b.equalFeeratePrefixWeight; + } + return a.maxOrder < b.maxOrder; +} + +function equalFeerate(a: LinearizationChunk, b: LinearizationChunk): boolean { + return a.fee * b.weight === b.fee * a.weight; +} + +function makeChunkHeapEntry(cluster: Cluster, chunkIndex: number, mempool: { [txid: string]: MempoolTransactionExtended }): ChunkHeapEntry { + const chunk = cluster.chunks[chunkIndex]; + let maxOrder = 0; + let sigops = 0; + for (const tx of chunk.txs) { + if (tx.order > maxOrder) { + maxOrder = tx.order; + } + const mempoolTx = mempool[tx.txid]; + if (mempoolTx) { + sigops += mempoolTx.sigops || 0; + } + } + let prefixWeight = chunk.weight; + for (let i = chunkIndex - 1; i >= 0; i--) { + if (equalFeerate(cluster.chunks[i], chunk)) { + prefixWeight += cluster.chunks[i].weight; + } else { + break; + } + } + return { + fee: chunk.fee, + weight: chunk.weight, + sigops, + equalFeeratePrefixWeight: prefixWeight, + maxOrder, + clusterId: cluster.id, + chunkIndex, + }; +} + +function buildChunkHeap(clusters: Map, mempool: { [txid: string]: MempoolTransactionExtended }): PairingHeap { + const heap = new PairingHeap(chunkHeapHigherPriority); + for (const cluster of clusters.values()) { + if (cluster.chunks.length > 0) { + heap.add(makeChunkHeapEntry(cluster, 0, mempool)); + } + } + return heap; +} + +export function assembleBlocks( + n: number, + clusters: Map, + mempool: { [txid: string]: MempoolTransactionExtended }, + enforceLimit: boolean, +): ProjectedBlock[] { + const heap = buildChunkHeap(clusters, mempool); + const blocks: ProjectedBlock[] = []; + for (let blockIdx = 0; blockIdx < n; blockIdx++) { + const limited = enforceLimit || blockIdx < n - 1; + const maxWeight = limited ? BLOCK_WEIGHT_UNITS : Infinity; + const maxSigops = limited ? MAX_BLOCK_SIGOPS_COST : Infinity; + const block = fillBlock(heap, clusters, mempool, maxWeight, maxSigops); + if (block.txids.length === 0) { + break; + } + blocks.push(block); + } + return blocks; +} + +function fillBlock( + heap: PairingHeap, + clusters: Map, + mempool: { [txid: string]: MempoolTransactionExtended }, + maxWeight: number, + maxSigops: number, +): ProjectedBlock { + const block: ProjectedBlock = { txids: [], weight: COINBASE_RESERVED_WEIGHT, sigops: 0 }; + const deferred: ChunkHeapEntry[] = []; + let consecutiveFailed = 0; + let full = false; + + while (!heap.isEmpty() && !full) { + const entry = heap.pop() as ChunkHeapEntry; + const cluster = clusters.get(entry.clusterId); + const chunk = cluster?.chunks[entry.chunkIndex]; + + if (!cluster || !chunk) { + // stale entry + } else if (block.weight + entry.weight < maxWeight + && block.sigops + entry.sigops < maxSigops) { + consecutiveFailed = 0; + block.weight += chunkWeight(chunk, mempool); + block.sigops += entry.sigops; + for (const tx of chunk.txs) { + block.txids.push(tx.txid); + } + if (entry.chunkIndex + 1 < cluster.chunks.length) { + heap.add(makeChunkHeapEntry(cluster, entry.chunkIndex + 1, mempool)); + } + } else { + deferred.push(entry); + consecutiveFailed++; + if (consecutiveFailed > MAX_CONSECUTIVE_FAILURES + && block.weight + MAX_WASTED_WEIGHT > maxWeight) { + full = true; + } + } + } + + for (const entry of deferred) { + heap.add(entry); + } + + return block; +} + +function chunkWeight( + chunk: LinearizationChunk, + mempool: { [txid: string]: MempoolTransactionExtended }, +): number { + let weight = 0; + for (const clusterTx of chunk.txs) { + const mempoolTx = mempool[clusterTx.txid]; + if (mempoolTx) { + weight += mempoolTx.weight; + } + } + return weight; +} diff --git a/backend/src/cluster-mempool/cluster-mempool.ts b/backend/src/cluster-mempool/cluster-mempool.ts new file mode 100644 index 000000000..92e1bd5fa --- /dev/null +++ b/backend/src/cluster-mempool/cluster-mempool.ts @@ -0,0 +1,709 @@ +import { ClusterTx, DepGraph, sortTopological, subgraph } from './depgraph'; +import { linearizeCluster, LinearizationChunk } from './linearize'; +import { ProjectedBlock, assembleBlocks } from './block-builder'; +import { Ancestor, CpfpClusterData, CpfpClusterTx, MempoolTransactionExtended } from '../mempool.interfaces'; +import logger from '../logger'; + +export interface MempoolDiff { + added: MempoolTransactionExtended[]; + removed: MempoolTransactionExtended[]; + accelerations: { [txid: string]: { feeDelta: number } }; +} + +export interface ClusterInfo { + clusterId: number; + chunkIndex: number; + chunkFeerate: number; +} + +export { ProjectedBlock }; + +export interface Cluster { + id: number; + depgraph: DepGraph; + txs: Map; + linearization: ClusterTx[]; + chunks: LinearizationChunk[]; + dirty: boolean; +} + +export interface TxCpfpData { + effectiveFeePerVsize: number; + clusterId: number; + chunkIndex: number; + cpfpDirty: boolean; + cpfpChecked: boolean; + ancestors: Ancestor[]; + descendants: Ancestor[]; +} + +const DEFAULT_COST_BUDGET = 75000; + +export class ClusterMempool { + private clusters = new Map(); + private txToCluster = new Map(); + private spentBy = new Map(); + private mempool: Readonly<{ [txid: string]: MempoolTransactionExtended }>; + private accelerations: { [txid: string]: { feeDelta: number } } = {}; + private nextClusterId = 0; + private modifyTxs: boolean; + private costBudget: number = DEFAULT_COST_BUDGET; + + constructor(mempool: { [txid: string]: MempoolTransactionExtended }, accelerations?: { [txid: string]: { feeDelta: number } }, modifyTxs: boolean = true, costBudget: number = DEFAULT_COST_BUDGET) { + this.mempool = mempool; + if (accelerations) { + this.accelerations = accelerations; + } + this.modifyTxs = modifyTxs; + this.costBudget = costBudget; + this.buildFromMempool(); + } + + applyMempoolChange(diff: MempoolDiff): void { + this.processRemovals(diff.removed); + this.splitDisconnectedClusters(); + this.processAccelerationChanges(diff.accelerations); + this.processAdditions(diff.added); + this.relinearizeDirtyClusters(); + } + + getBlocks(n: number, enforceLimit = false): ProjectedBlock[] { + return assembleBlocks(n, this.clusters, this.mempool, enforceLimit); + } + + getCluster(clusterId: number): CpfpClusterData | null { + const cluster = this.clusters.get(clusterId); + if (!cluster) { + return null; + } + return this.buildClusterData(cluster); + } + + getClusterInfo(txid: string): ClusterInfo | null { + const match = this.getClusterForTx(txid); + if (!match) { + return null; + } + return this.findChunkInfo(match.cluster, match.clusterTx); + } + + getClusterForApi(txid: string): (CpfpClusterData & { chunkIndex: number }) | null { + const info = this.getClusterInfo(txid); + if (!info) { + return null; + } + const cluster = this.getCluster(info.clusterId); + if (!cluster || cluster.txs.length <= 1) { + return null; + } + return { ...cluster, chunkIndex: info.chunkIndex }; + } + + getCpfpDataForTx(txid: string): TxCpfpData | null { + const clusterInfo = this.getClusterForTx(txid); + if (!clusterInfo) { + return null; + } + const chunkInfo = this.findChunkInfo(clusterInfo.cluster, clusterInfo.clusterTx); + if (!chunkInfo) { + return null; + } + const chunk = clusterInfo.cluster.chunks[chunkInfo?.chunkIndex]; + const chunkSet = chunk.txs.length > 1 ? new Set(chunk.txs) : null; + return { + effectiveFeePerVsize: chunkInfo.chunkFeerate, + clusterId: clusterInfo.cluster.id, + chunkIndex: chunkInfo.chunkIndex, + cpfpDirty: true, + cpfpChecked: true, + ancestors: chunkSet ? this.getChunkRelatives(clusterInfo.clusterTx, chunkSet, 'ancestors') : [], + descendants: chunkSet ? this.getChunkRelatives(clusterInfo.clusterTx, chunkSet, 'descendants') : [], + }; + } + + getClusterCount(): number { + return this.clusters.size; + } + + getTxCount(): number { + return this.txToCluster.size; + } + + private getClusterForTx(txid: string): { cluster: Cluster; clusterTx: ClusterTx } | null { + const clusterId = this.txToCluster.get(txid); + if (clusterId === undefined) { + return null; + } + const cluster = this.clusters.get(clusterId); + if (!cluster) { + return null; + } + const clusterTx = cluster.txs.get(txid); + if (!clusterTx) { + return null; + } + return { cluster, clusterTx }; + } + + private buildFromMempool(): void { + const parentMap = this.buildRelativeMaps(); + const components = this.findMempoolComponents(parentMap); + for (const component of components) { + this.createClusterFromTxids(component, parentMap); + } + } + + private buildRelativeMaps(): Map> { + const parentMap = new Map>(); + this.spentBy.clear(); + for (const txid in this.mempool) { + const tx = this.mempool[txid]; + const txParents = new Set(); + for (const vin of tx.vin) { + if (!vin.is_coinbase && this.mempool[vin.txid]) { + txParents.add(vin.txid); + this.spentBy.set(`${vin.txid}:${vin.vout}`, txid); + } + } + if (txParents.size > 0) { + parentMap.set(txid, txParents); + } + } + return parentMap; + } + + private findMempoolComponents(parentMap: Map>): Set[] { + const visited = new Set(); + const components: Set[] = []; + + for (const txid in this.mempool) { + if (!visited.has(txid)) { + const component = this.dfsComponent(txid, visited, parentMap); + components.push(component); + } + } + return components; + } + + private dfsComponent( + startTxid: string, + visited: Set, + parentMap: Map> + ): Set { + const component = new Set(); + const stack = [startTxid]; + while (stack.length > 0) { + const current = stack.pop(); + if (current !== undefined && !visited.has(current)) { + visited.add(current); + component.add(current); + + const txParents = parentMap.get(current); + if (txParents) { + for (const p of txParents) { + if (!visited.has(p)) { + stack.push(p); + } + } + } + + const tx = this.mempool[current]; + if (tx) { + for (let vout = 0; vout < tx.vout.length; vout++) { + const child = this.spentBy.get(`${current}:${vout}`); + if (child && !visited.has(child)) { + stack.push(child); + } + } + } + } + } + return component; + } + + private effectiveFee(txid: string, tx: MempoolTransactionExtended): number { + return tx.fee + (this.accelerations[txid]?.feeDelta || 0); + } + + private adjustedWeight(tx: MempoolTransactionExtended): number { + return Math.max(tx.weight, (tx.sigops || 0) * 20); + } + + private createClusterFromTxids( + txids: Set, + parentMap: Map> + ): Cluster | null { + const clusterId = this.nextClusterId++; + const depgraph = new DepGraph(); + const txMap = new Map(); + + for (const txid of txids) { + const tx = this.mempool[txid]; + if (!tx) { + logger.warn(`Warning: missing mempool tx ${txid} during cluster creation, skipping`); + return null; + } + const clusterTx = depgraph.addTransaction(txid, this.effectiveFee(txid, tx), this.adjustedWeight(tx), tx.order ?? 0); + txMap.set(txid, clusterTx); + } + + for (const txid of txids) { + const txParents = parentMap.get(txid); + if (txParents) { + for (const parentTxid of txParents) { + if (txids.has(parentTxid)) { + const parentTx = txMap.get(parentTxid); + const childTx = txMap.get(txid); + if (parentTx && childTx) { + depgraph.addDependency(parentTx, childTx); + } + } + } + } + } + + const { linearization, chunks } = linearizeCluster(depgraph.getTxs(), this.costBudget); + + const cluster: Cluster = { + id: clusterId, + depgraph, + txs: txMap, + linearization, + chunks, + dirty: false, + }; + + this.clusters.set(clusterId, cluster); + for (const txid of txids) { + this.txToCluster.set(txid, clusterId); + } + + if (this.modifyTxs) { + this.writeBackCluster(cluster); + } + return cluster; + } + + public writeBackClusters(): void { + for (const cluster of this.clusters.values()) { + this.writeBackCluster(cluster); + } + } + + private writeBackCluster(cluster: Cluster): void { + for (let chunkIdx = 0; chunkIdx < cluster.chunks.length; chunkIdx++) { + const chunk = cluster.chunks[chunkIdx]; + const chunkFeerate = chunk.weight > 0 ? (chunk.fee * 4) / chunk.weight : 0; + const chunkSet = chunk.txs.length > 1 ? new Set(chunk.txs) : null; + for (const clusterTx of chunk.txs) { + this.writeBackTx(cluster, clusterTx, chunkIdx, chunkFeerate, chunkSet); + } + } + } + + private writeBackTx( + cluster: Cluster, + clusterTx: ClusterTx, + chunkIdx: number, + chunkFeerate: number, + chunkSet: Set | null + ): void { + const txid = clusterTx.txid; + if (!this.mempool[txid]) { + logger.warn(`ClusterMempool.writeBackTx: ${txid} missing from mempool (cluster ${cluster.id})`); + return; + } + const tx = this.mempool[txid]; + if (tx.effectiveFeePerVsize !== chunkFeerate || tx.clusterId !== cluster.id) { + tx.cpfpDirty = true; + } + tx.effectiveFeePerVsize = chunkFeerate; + tx.clusterId = cluster.id; + tx.chunkIndex = chunkIdx; + tx.cpfpChecked = true; + + if (chunkSet) { + tx.ancestors = this.getChunkRelatives(clusterTx, chunkSet, 'ancestors'); + tx.descendants = this.getChunkRelatives(clusterTx, chunkSet, 'descendants'); + } else { + tx.ancestors = []; + tx.descendants = []; + } + } + + private getChunkRelatives( + clusterTx: ClusterTx, + chunkSet: Set, + direction: 'ancestors' | 'descendants' + ): { txid: string; fee: number; weight: number }[] { + const relatives: { txid: string; fee: number; weight: number }[] = []; + const related = direction === 'ancestors' ? clusterTx.ancestors : clusterTx.descendants; + for (const rel of related) { + if (rel !== clusterTx && chunkSet.has(rel)) { + const mempoolTx = this.mempool[rel.txid]; + if (mempoolTx) { + relatives.push({ txid: rel.txid, fee: mempoolTx.fee, weight: mempoolTx.weight }); + } else { + logger.warn(`ClusterMempool.getChunkRelatives: ${rel.txid} missing from mempool`); + } + } + } + return relatives; + } + + private processRemovals(removed: MempoolTransactionExtended[]): void { + for (const tx of removed) { + for (const vin of tx.vin) { + if (!vin.is_coinbase) { + const spentOutpoint = `${vin.txid}:${vin.vout}`; + if (this.spentBy.get(spentOutpoint) === tx.txid) { + this.spentBy.delete(spentOutpoint); + } + } + } + } + + for (const tx of removed) { + const match = this.getClusterForTx(tx.txid); + if (match) { + match.cluster.depgraph.removeTransactions(new Set([match.clusterTx])); + match.cluster.txs.delete(tx.txid); + match.cluster.linearization = match.cluster.linearization.filter(t => t !== match.clusterTx); + this.txToCluster.delete(tx.txid); + match.cluster.dirty = true; + } + } + } + + private splitDisconnectedClusters(): void { + for (const [clusterId, cluster] of this.clusters.entries()) { + if (cluster.dirty) { + if (cluster.depgraph.size === 0) { + this.clusters.delete(clusterId); + } else { + const components = cluster.depgraph.findConnectedComponents(); + if (components.length > 1) { + this.clusters.delete(clusterId); + for (const component of components) { + this.splitComponentToCluster(cluster, component); + } + } + } + } + } + } + + private splitComponentToCluster(sourceCluster: Cluster, component: Set): void { + const newClusterId = this.nextClusterId++; + const { depgraph: newDepgraph, txMap } = subgraph(component); + + const newTxs = new Map(); + for (const oldTx of component) { + const newTx = txMap.get(oldTx); + if (newTx) { + newTxs.set(oldTx.txid, newTx); + this.txToCluster.set(oldTx.txid, newClusterId); + } + } + + const newLinearization: ClusterTx[] = []; + for (const oldTx of sourceCluster.linearization) { + if (component.has(oldTx)) { + const newTx = txMap.get(oldTx); + if (newTx) { + newLinearization.push(newTx); + } + } + } + + const newCluster: Cluster = { + id: newClusterId, + dirty: true, + depgraph: newDepgraph, + txs: newTxs, + linearization: newLinearization, + chunks: [], + }; + this.clusters.set(newClusterId, newCluster); + } + + private processAdditions(added: MempoolTransactionExtended[]): void { + for (const tx of added) { + const txid = tx.txid; + + // sanity check for duplicate transactions + if (this.getClusterForTx(txid) !== null) { + logger.warn(`ClusterMempool.processAdditions: ${txid} was already added, skipping`); + continue; + } + + for (const vin of tx.vin) { + if (!vin.is_coinbase) { + this.spentBy.set(`${vin.txid}:${vin.vout}`, txid); + } + } + + const { relatedClusterIds, parentTxids, childTxids } = this.findRelatedClusters(tx); + + if (relatedClusterIds.size === 0) { + this.addSingletonCluster(tx); + } else if (relatedClusterIds.size === 1) { + this.addToExistingCluster(tx, relatedClusterIds, parentTxids, childTxids); + } else { + this.mergeAndAddToCluster(tx, relatedClusterIds, parentTxids, childTxids); + } + } + } + + private findRelatedClusters(tx: MempoolTransactionExtended): { + relatedClusterIds: Set; + parentTxids: string[]; + childTxids: string[]; + } { + const relatedClusterIds = new Set(); + const parentTxids: string[] = []; + const childTxids: string[] = []; + + for (const vin of tx.vin) { + if (!vin.is_coinbase && this.mempool[vin.txid]) { + const parentCluster = this.txToCluster.get(vin.txid); + if (parentCluster !== undefined) { + relatedClusterIds.add(parentCluster); + parentTxids.push(vin.txid); + } + } + } + + for (let vout = 0; vout < tx.vout.length; vout++) { + const childTxid = this.spentBy.get(`${tx.txid}:${vout}`); + if (childTxid && this.mempool[childTxid]) { + const childCluster = this.txToCluster.get(childTxid); + if (childCluster !== undefined) { + relatedClusterIds.add(childCluster); + childTxids.push(childTxid); + } + } + } + + return { relatedClusterIds, parentTxids, childTxids }; + } + + private addSingletonCluster(tx: MempoolTransactionExtended): void { + const txid = tx.txid; + const clusterId = this.nextClusterId++; + const depgraph = new DepGraph(); + const clusterTx = depgraph.addTransaction(txid, this.effectiveFee(txid, tx), this.adjustedWeight(tx), tx.order ?? 0); + + const cluster: Cluster = { + id: clusterId, + dirty: true, + depgraph, + txs: new Map([[txid, clusterTx]]), + linearization: [clusterTx], + chunks: [], + }; + this.clusters.set(clusterId, cluster); + this.txToCluster.set(txid, clusterId); + } + + private addToExistingCluster( + tx: MempoolTransactionExtended, + relatedClusterIds: Set, + parentTxids: string[], + childTxids: string[], + ): void { + const txid = tx.txid; + const clusterId = relatedClusterIds.values().next().value; + const cluster = this.clusters.get(clusterId); + if (!cluster) { + return; + } + const clusterTx = cluster.depgraph.addTransaction(txid, this.effectiveFee(txid, tx), this.adjustedWeight(tx), tx.order ?? 0); + cluster.txs.set(txid, clusterTx); + cluster.linearization.push(clusterTx); + this.txToCluster.set(txid, clusterId); + + this.addParentDeps(cluster, clusterTx, parentTxids); + this.addChildDeps(cluster, clusterTx, childTxids); + cluster.dirty = true; + } + + private mergeAndAddToCluster( + tx: MempoolTransactionExtended, + relatedClusterIds: Set, + parentTxids: string[], + childTxids: string[], + ): void { + const clusterIterator = relatedClusterIds.values(); + const primaryId: number = clusterIterator.next().value; + const primary = this.clusters.get(primaryId); + if (!primary) { + return; + } + + for (const clusterId of clusterIterator) { + const other = this.clusters.get(clusterId); + if (other) { + this.mergeClusterInto(primary, other); + this.clusters.delete(clusterId); + } + } + + const clusterTx = primary.depgraph.addTransaction(tx.txid, this.effectiveFee(tx.txid, tx), this.adjustedWeight(tx), tx.order ?? 0); + primary.txs.set(tx.txid, clusterTx); + primary.linearization.push(clusterTx); + this.txToCluster.set(tx.txid, primaryId); + + this.addParentDeps(primary, clusterTx, parentTxids); + this.addChildDeps(primary, clusterTx, childTxids); + primary.dirty = true; + } + + private addParentDeps(cluster: Cluster, childTx: ClusterTx, parentTxids: string[]): void { + for (const parentTxid of parentTxids) { + const parentTx = cluster.txs.get(parentTxid); + if (parentTx) { + cluster.depgraph.addDependency(parentTx, childTx); + } + } + } + + private addChildDeps(cluster: Cluster, parentTx: ClusterTx, childTxids: string[]): void { + for (const childTxid of childTxids) { + const childTx = cluster.txs.get(childTxid); + if (childTx) { + cluster.depgraph.addDependency(parentTx, childTx); + } + } + } + + private mergeClusterInto(primary: Cluster, other: Cluster): void { + for (const [txid, otherTx] of other.txs) { + const newTx = primary.depgraph.addTransaction(txid, otherTx.effectiveFee, otherTx.weight, otherTx.order); + primary.txs.set(txid, newTx); + this.txToCluster.set(txid, primary.id); + } + + for (const otherTx of other.depgraph.getTxs()) { + for (const parent of otherTx.parents) { + const newChild = primary.txs.get(otherTx.txid); + const newParent = primary.txs.get(parent.txid); + if (newChild && newParent) { + primary.depgraph.addDependency(newParent, newChild); + } + } + } + + for (const otherTx of other.linearization) { + const newTx = primary.txs.get(otherTx.txid); + if (newTx) { + primary.linearization.push(newTx); + } + } + } + + private processAccelerationChanges(newAccelerations: { [txid: string]: { feeDelta: number } }): void { + const changed = new Set(); + for (const txid in newAccelerations) { + if ((newAccelerations[txid]?.feeDelta || 0) !== (this.accelerations[txid]?.feeDelta || 0)) { + changed.add(txid); + } + } + for (const txid in this.accelerations) { + if (!newAccelerations[txid]) { + changed.add(txid); + } + } + this.accelerations = newAccelerations; + for (const txid of changed) { + const tx = this.mempool[txid]; + if (!tx) { + continue; + } + const match = this.getClusterForTx(txid); + if (match) { + match.clusterTx.effectiveFee = this.effectiveFee(txid, tx); + match.cluster.dirty = true; + } + } + } + + private relinearizeDirtyClusters(): void { + for (const [clusterId, cluster] of this.clusters.entries()) { + if (cluster.dirty) { + cluster.dirty = false; + const newId = this.nextClusterId++; + this.clusters.delete(clusterId); + cluster.id = newId; + this.clusters.set(newId, cluster); + for (const txid of cluster.txs.keys()) { + this.txToCluster.set(txid, newId); + } + + const { linearization, chunks } = linearizeCluster( + cluster.depgraph.getTxs(), + this.costBudget, + cluster.linearization, + ); + cluster.linearization = linearization; + cluster.chunks = chunks; + + if (this.modifyTxs) { + this.writeBackCluster(cluster); + } + } + } + } + + private buildClusterData(cluster: Cluster): CpfpClusterData { + const txs: CpfpClusterTx[] = []; + const txToFlatIdx = new Map(); + + for (const chunk of cluster.chunks) { + const ordered = sortTopological(new Set(chunk.txs)); + for (const clusterTx of ordered) { + if (this.mempool[clusterTx.txid]) { + txToFlatIdx.set(clusterTx, txs.length); + const parents: number[] = []; + for (const parentTx of clusterTx.parents) { + const flatIdx = txToFlatIdx.get(parentTx); + if (flatIdx !== undefined) { + parents.push(flatIdx); + } + } + const mempoolTx = this.mempool[clusterTx.txid]; + txs.push({ txid: clusterTx.txid, fee: mempoolTx.fee, weight: mempoolTx.weight, parents }); + } else { + logger.warn(`ClusterMempool.buildClusterData: ${clusterTx.txid} missing from mempool (cluster ${cluster.id})`); + } + } + } + + let offset = 0; + const chunks = cluster.chunks.map(chunk => { + const count = chunk.txs.length; + const chunkEntry = { + txs: Array.from({ length: count }, (_, i) => offset + i), + feerate: chunk.weight > 0 ? (chunk.fee * 4) / chunk.weight : 0, + }; + offset += count; + return chunkEntry; + }); + + return { txs, chunks }; + } + + private findChunkInfo(cluster: Cluster, tx: ClusterTx): ClusterInfo | null { + for (let chunkIdx = 0; chunkIdx < cluster.chunks.length; chunkIdx++) { + const chunk = cluster.chunks[chunkIdx]; + if (chunk.txs.includes(tx)) { + return { + clusterId: cluster.id, + chunkIndex: chunkIdx, + chunkFeerate: chunk.weight > 0 ? (chunk.fee * 4) / chunk.weight : 0, + }; + } + } + return null; + } +} diff --git a/backend/src/cluster-mempool/depgraph.ts b/backend/src/cluster-mempool/depgraph.ts new file mode 100644 index 000000000..76e98e3f2 --- /dev/null +++ b/backend/src/cluster-mempool/depgraph.ts @@ -0,0 +1,165 @@ +import logger from '../logger'; + +export class ClusterTx { + txid: string; + effectiveFee: number; + weight: number; + order: number; + ancestors: Set; + descendants: Set; + parents: Set; + children: Set; + + constructor(txid: string, effectiveFee: number, weight: number, order: number) { + this.txid = txid; + this.effectiveFee = effectiveFee; + this.weight = weight; + this.order = order; + this.ancestors = new Set([this]); + this.descendants = new Set([this]); + this.parents = new Set(); + this.children = new Set(); + } +} + +export class DepGraph { + private txs: Set = new Set(); + + get size(): number { + return this.txs.size; + } + + addTransaction(txid: string, fee: number, weight: number, order: number = 0): ClusterTx { + const tx = new ClusterTx(txid, fee, weight, order); + this.txs.add(tx); + return tx; + } + + addDependency(parent: ClusterTx, child: ClusterTx): void { + if (!this.txs.has(parent) || !this.txs.has(child)) { + logger.warn(`Warning: invalid dependency, skipping`); + return; + } + + parent.children.add(child); + child.parents.add(parent); + + if (child.ancestors.has(parent)) { + return; + } + + for (const descendant of child.descendants) { + for (const ancestor of parent.ancestors) { + descendant.ancestors.add(ancestor); + ancestor.descendants.add(descendant); + } + } + } + + removeTransactions(toRemove: Set): void { + for (const tx of toRemove) { + for (const parent of tx.parents) { + parent.children.delete(tx); + } + for (const child of tx.children) { + child.parents.delete(tx); + } + this.txs.delete(tx); + } + + for (const tx of this.txs) { + for (const removed of toRemove) { + tx.ancestors.delete(removed); + tx.descendants.delete(removed); + } + } + + this.rederiveAncestorsDescendants(); + } + + private rederiveAncestorsDescendants(): void { + const ordered = [...this.txs].sort((a, b) => a.ancestors.size - b.ancestors.size); + for (const tx of this.txs) { + tx.ancestors = new Set([tx]); + tx.descendants = new Set([tx]); + } + for (const tx of ordered) { + for (const parent of tx.parents) { + for (const ancestor of parent.ancestors) { + tx.ancestors.add(ancestor); + } + } + for (const ancestor of tx.ancestors) { + ancestor.descendants.add(tx); + } + } + } + + hasTx(tx: ClusterTx): boolean { + return this.txs.has(tx); + } + + getTxs(): Set { + return this.txs; + } + + findConnectedComponents(): Set[] { + const visited = new Set(); + const components: Set[] = []; + + for (const tx of this.txs) { + if (!visited.has(tx)) { + const component = new Set(); + const stack: ClusterTx[] = [tx]; + while (stack.length > 0) { + const node = stack.pop(); + if (node && !visited.has(node)) { + visited.add(node); + component.add(node); + for (const a of node.ancestors) { + if (!visited.has(a) && this.txs.has(a)) { + stack.push(a); + } + } + for (const d of node.descendants) { + if (!visited.has(d) && this.txs.has(d)) { + stack.push(d); + } + } + } + } + components.push(component); + } + } + return components; + } + +} + +export function sortTopological(subset: Set): ClusterTx[] { + return [...subset].sort((a, b) => a.ancestors.size - b.ancestors.size); +} + +export function subgraph(txSubset: Set): { depgraph: DepGraph; txMap: Map } { + const newGraph = new DepGraph(); + const txMap = new Map(); + + for (const oldTx of txSubset) { + const newTx = newGraph.addTransaction(oldTx.txid, oldTx.effectiveFee, oldTx.weight, oldTx.order); + txMap.set(oldTx, newTx); + } + + for (const oldTx of txSubset) { + for (const parent of oldTx.parents) { + if (txSubset.has(parent)) { + const newChild = txMap.get(oldTx); + const newParent = txMap.get(parent); + if (newChild && newParent) { + newGraph.addDependency(newParent, newChild); + } + } + } + } + + return { depgraph: newGraph, txMap }; +} diff --git a/backend/src/cluster-mempool/linearize.ts b/backend/src/cluster-mempool/linearize.ts new file mode 100644 index 000000000..8b1c0465d --- /dev/null +++ b/backend/src/cluster-mempool/linearize.ts @@ -0,0 +1,1314 @@ +import { ClusterTx } from './depgraph'; + +function higherFeerate(aFee: number, aWeight: number, bFee: number, bWeight: number): boolean { + return aFee * bWeight > bFee * aWeight; +} + +export interface LinearizationChunk { + txs: ClusterTx[]; + fee: number; + weight: number; +} + +export function chunkify(linearization: ClusterTx[]): LinearizationChunk[] { + const chunks: LinearizationChunk[] = []; + + for (const tx of linearization) { + chunks.push({ txs: [tx], fee: tx.effectiveFee, weight: tx.weight }); + + while (chunks.length >= 2) { + const last = chunks[chunks.length - 1]; + const prev = chunks[chunks.length - 2]; + if (higherFeerate(last.fee, last.weight, prev.fee, prev.weight)) { + prev.txs.push(...last.txs); + prev.fee += last.fee; + prev.weight += last.weight; + chunks.pop(); + } else { + break; + } + } + } + + return chunks; +} + +export function postLinearize(linearization: ClusterTx[]): ClusterTx[] { + if (linearization.length <= 1) { + return [...linearization]; + } + let result = [...linearization]; + result = postLinearizePass(result, true); + result = postLinearizePass(result, false); + return result; +} + +interface PostLinGroup { + txs: ClusterTx[]; + deps: Set; + fee: number; + weight: number; +} + +function postLinearizePass(lin: ClusterTx[], forward: boolean): ClusterTx[] { + const n = lin.length; + if (n <= 1) { + return [...lin]; + } + + const input = forward ? lin : [...lin].reverse(); + const feeMul = forward ? 1 : -1; + + const groups: PostLinGroup[] = []; + const seen = new Set(); + + for (const tx of input) { + const deps = new Set(); + const related = forward ? tx.parents : tx.children; + for (const r of related) { + if (seen.has(r)) { + deps.add(r); + } + } + seen.add(tx); + + groups.push({ + txs: [tx], + deps, + fee: tx.effectiveFee * feeMul, + weight: tx.weight, + }); + + let pos = groups.length - 1; + while (pos > 0) { + const cur = groups[pos]; + const prev = groups[pos - 1]; + if (groupsDepsOverlap(cur, prev)) { + mergeGroupIntoCurrent(groups, pos); + pos--; + } else if (higherFeerate(cur.fee, cur.weight, prev.fee, prev.weight)) { + swapAdjacentGroups(groups, pos); + pos--; + } else { + break; + } + } + } + + const result: ClusterTx[] = []; + for (const g of groups) { + for (const tx of g.txs) { + result.push(tx); + } + } + + if (!forward) { + result.reverse(); + } + + return result; +} + +function groupsDepsOverlap(cur: PostLinGroup, prev: PostLinGroup): boolean { + for (const tx of prev.txs) { + if (cur.deps.has(tx)) { + return true; + } + } + return false; +} + +function mergeGroupIntoCurrent(groups: PostLinGroup[], pos: number): void { + const cur = groups[pos]; + const prev = groups[pos - 1]; + prev.txs.push(...cur.txs); + for (const d of cur.deps) { + prev.deps.add(d); + } + prev.fee += cur.fee; + prev.weight += cur.weight; + groups.splice(pos, 1); +} + +function swapAdjacentGroups(groups: PostLinGroup[], pos: number): void { + const tmp = groups[pos]; + groups[pos] = groups[pos - 1]; + groups[pos - 1] = tmp; +} + +function pickRandomTx(txs: Set): ClusterTx | null { + const arr = [...txs]; + if (arr.length === 0) { + return null; + } + return arr[Math.floor(Math.random() * arr.length)]; +} + +interface SFLChunk { + id: number; + txs: Set; + fee: number; + weight: number; +} + +interface SFLDependency { + parent: ClusterTx; + child: ClusterTx; + active: boolean; +} + +const enum MergeDir { Up, Down, Both } + +interface SFLCost { cost: number; } + +export function spanningForestLinearize( + txs: Set, + costBudget: number, + existingLinearization?: ClusterTx[], +): ClusterTx[] { + const allTxs = [...txs]; + if (allTxs.length === 0) { + return []; + } + if (allTxs.length === 1) { + return [...allTxs]; + } + + const deps = collectDirectDeps(allTxs); + + if (deps.length === 0) { + return sortByFeerateDesc(allTxs); + } + + const { chunks, txToChunk, nextChunkId: startNextId } = initSFLChunks(allTxs); + + const cost: SFLCost = { cost: 87 * allTxs.length + 4 * deps.length }; + + if (existingLinearization && existingLinearization.length > 0) { + for (const tx of existingLinearization) { + const chunkId = txs.has(tx) ? txToChunk.get(tx) : undefined; + if (chunkId !== undefined) { + mergeUpwards(chunkId, deps, chunks, txToChunk, cost); + } + } + } + + let nextId = makeTopological(deps, chunks, txToChunk, startNextId, cost); + if (cost.cost < costBudget) { + nextId = optimizeSFL(deps, chunks, txToChunk, nextId, costBudget, cost); + } + if (cost.cost < costBudget) { + minimizeSFL(deps, chunks, txToChunk, nextId, costBudget, cost); + } + + return extractLinearization(chunks, txToChunk); +} + +function collectDirectDeps(txs: ClusterTx[]): SFLDependency[] { + const deps: SFLDependency[] = []; + for (const tx of txs) { + for (const parent of tx.parents) { + deps.push({ parent, child: tx, active: false }); + } + } + return deps; +} + +function sortByFeerateDesc(txs: ClusterTx[]): ClusterTx[] { + return [...txs].sort((a, b) => { + if (higherFeerate(a.effectiveFee, a.weight, b.effectiveFee, b.weight)) { + return -1; + } + if (higherFeerate(b.effectiveFee, b.weight, a.effectiveFee, a.weight)) { + return 1; + } + return a.order - b.order; + }); +} + +function initSFLChunks( + txs: ClusterTx[] +): { chunks: Map; txToChunk: Map; nextChunkId: number } { + let nextChunkId = 0; + const txToChunk = new Map(); + const chunks = new Map(); + + for (const tx of txs) { + const chunkId = nextChunkId++; + chunks.set(chunkId, { + id: chunkId, + txs: new Set([tx]), + fee: tx.effectiveFee, + weight: tx.weight, + }); + txToChunk.set(tx, chunkId); + } + + return { chunks, txToChunk, nextChunkId }; +} + +function mergeChunks( + dstId: number, + srcId: number, + chunks: Map, + txToChunk: Map +): void { + const dst = chunks.get(dstId); + const src = chunks.get(srcId); + if (!dst || !src) { + return; + } + for (const tx of src.txs) { + dst.txs.add(tx); + txToChunk.set(tx, dstId); + } + dst.fee += src.fee; + dst.weight += src.weight; + chunks.delete(srcId); +} + +function activateInternalDeps( + chunkId: number, + deps: SFLDependency[], + txToChunk: Map, + cost: SFLCost, + chunks?: Map, +): void { + for (const d of deps) { + if (!d.active) { + const c1 = txToChunk.get(d.parent); + const c2 = txToChunk.get(d.child); + if (c1 === chunkId && c2 === chunkId) { + d.active = true; + } + } + } + const mergedChunkSize = chunks?.get(chunkId)?.txs.size ?? 0; + cost.cost += 10 * mergedChunkSize + 1; +} + +function pickMergeCandidateUp( + chunkId: number, + chunk: SFLChunk, + deps: SFLDependency[], + chunks: Map, + txToChunk: Map, + cost: SFLCost, +): number | null { + let bestChunkId: number | null = null; + let bestFee = 0; + let bestWeight = 0; + let bestTiebreak = 0; + const visited = new Set(); + for (const dep of deps) { + if (!dep.active) { + const childChunk = txToChunk.get(dep.child); + const parentChunkId = txToChunk.get(dep.parent); + if (childChunk === chunkId && parentChunkId !== chunkId && parentChunkId !== undefined) { + visited.add(parentChunkId); + const pChunk = chunks.get(parentChunkId); + if (pChunk && !higherFeerate(pChunk.fee, pChunk.weight, chunk.fee, chunk.weight)) { + const tiebreak = Math.random(); + if (bestChunkId === null + || higherFeerate(bestFee, bestWeight, pChunk.fee, pChunk.weight) + || (!higherFeerate(pChunk.fee, pChunk.weight, bestFee, bestWeight) && tiebreak > bestTiebreak)) { + bestChunkId = parentChunkId; + bestFee = pChunk.fee; + bestWeight = pChunk.weight; + bestTiebreak = tiebreak; + } + } + } + } + } + cost.cost += 8 * visited.size; + return bestChunkId; +} + +function mergeStep( + dir: MergeDir.Up | MergeDir.Down, + chunkId: number, + deps: SFLDependency[], + chunks: Map, + txToChunk: Map, + cost: SFLCost, +): number | null { + if (dir === MergeDir.Up) { + return mergeStepUp(chunkId, deps, chunks, txToChunk, cost); + } + return mergeStepDown(chunkId, deps, chunks, txToChunk, cost); +} + +function mergeStepUp( + chunkId: number, + deps: SFLDependency[], + chunks: Map, + txToChunk: Map, + cost: SFLCost, +): number | null { + const chunk = chunks.get(chunkId); + if (!chunk) { + return null; + } + const parentChunkId = pickMergeCandidateUp(chunkId, chunk, deps, chunks, txToChunk, cost); + if (parentChunkId === null) { + return null; + } + const dep = pickRandomCrossChunkDep(parentChunkId, chunkId, deps, txToChunk, chunks, cost); + if (!dep) { + return null; + } + dep.active = true; + mergeChunks(chunkId, parentChunkId, chunks, txToChunk); + activateInternalDeps(chunkId, deps, txToChunk, cost, chunks); + return chunkId; +} + +function mergeStepDown( + chunkId: number, + deps: SFLDependency[], + chunks: Map, + txToChunk: Map, + cost: SFLCost, +): number | null { + const chunk = chunks.get(chunkId); + if (!chunk) { + return null; + } + const childChunkId = pickMergeCandidateDown(chunkId, chunk, deps, chunks, txToChunk, cost); + if (childChunkId === null) { + return null; + } + const dep = pickRandomCrossChunkDep(chunkId, childChunkId, deps, txToChunk, chunks, cost); + if (!dep) { + return null; + } + dep.active = true; + mergeChunks(chunkId, childChunkId, chunks, txToChunk); + activateInternalDeps(chunkId, deps, txToChunk, cost, chunks); + return chunkId; +} + +function makeTopological( + deps: SFLDependency[], + chunks: Map, + txToChunk: Map, + nextChunkId: number, + cost: SFLCost, +): number { + const queue: number[] = []; + const onQueue = new Set(); + for (const [chunkId] of chunks) { + queue.push(chunkId); + const j = Math.floor(Math.random() * queue.length); + if (j !== queue.length - 1) { + [queue[queue.length - 1], queue[j]] = [queue[j], queue[queue.length - 1]]; + } + onQueue.add(chunkId); + } + + const mergedChunks = new Set(); + const initDir: MergeDir = Math.random() < 0.5 ? MergeDir.Up : MergeDir.Down; + let numSteps = 0; + + while (queue.length > 0) { + const chunkId = queue.shift(); + if (chunkId === undefined) { + break; + } + onQueue.delete(chunkId); + if (!chunks.has(chunkId)) { + continue; + } + numSteps++; + + const dir = mergedChunks.has(chunkId) ? MergeDir.Both : initDir; + const first = Math.random() < 0.5 ? MergeDir.Up : MergeDir.Down; + const second = first === MergeDir.Up ? MergeDir.Down : MergeDir.Up; + + let result: number | null = null; + if (dir === MergeDir.Both || dir === first) { + result = mergeStep(first, chunkId, deps, chunks, txToChunk, cost); + } + if (result === null && (dir === MergeDir.Both || dir === second)) { + result = mergeStep(second, chunkId, deps, chunks, txToChunk, cost); + } + + if (result !== null) { + if (!onQueue.has(result)) { + onQueue.add(result); + queue.push(result); + } + mergedChunks.add(result); + } + } + + cost.cost += 20 * chunks.size + 28 * numSteps; + + return nextChunkId; +} + +function mergeUpwards( + chunkId: number, + deps: SFLDependency[], + chunks: Map, + txToChunk: Map, + cost: SFLCost, +): void { + let done = false; + while (!done) { + done = mergeStepUp(chunkId, deps, chunks, txToChunk, cost) === null; + } +} + +function pickMergeCandidateDown( + chunkId: number, + chunk: SFLChunk, + deps: SFLDependency[], + chunks: Map, + txToChunk: Map, + cost: SFLCost, +): number | null { + let bestChunkId: number | null = null; + let bestFee = 0; + let bestWeight = 0; + let bestTiebreak = 0; + const visited = new Set(); + for (const dep of deps) { + if (!dep.active) { + const parentChunk = txToChunk.get(dep.parent); + const childChunkId = txToChunk.get(dep.child); + if (parentChunk === chunkId && childChunkId !== chunkId && childChunkId !== undefined) { + visited.add(childChunkId); + const cChunk = chunks.get(childChunkId); + if (cChunk && !higherFeerate(chunk.fee, chunk.weight, cChunk.fee, cChunk.weight)) { + const tiebreak = Math.random(); + if (bestChunkId === null + || higherFeerate(cChunk.fee, cChunk.weight, bestFee, bestWeight) + || (!higherFeerate(bestFee, bestWeight, cChunk.fee, cChunk.weight) && tiebreak > bestTiebreak)) { + bestChunkId = childChunkId; + bestFee = cChunk.fee; + bestWeight = cChunk.weight; + bestTiebreak = tiebreak; + } + } + } + } + } + cost.cost += 8 * visited.size; + return bestChunkId; +} + +function pickRandomCrossChunkDep( + topChunkId: number, + bottomChunkId: number, + deps: SFLDependency[], + txToChunk: Map, + chunks: Map, + cost: SFLCost, +): SFLDependency | null { + const topChunk = chunks.get(topChunkId); + const candidates: SFLDependency[] = []; + let scanSteps = 0; + for (const d of deps) { + if (!d.active) { + const pChunk = txToChunk.get(d.parent); + const cChunk = txToChunk.get(d.child); + if (pChunk === topChunkId && cChunk === bottomChunkId) { + candidates.push(d); + } + scanSteps++; + } + } + cost.cost += 2 * (topChunk?.txs.size ?? 0); + cost.cost += 3 * scanSteps + 5; + if (candidates.length === 0) { + return null; + } + return candidates[Math.floor(Math.random() * candidates.length)]; +} + +function mergeDownwards( + chunkId: number, + deps: SFLDependency[], + chunks: Map, + txToChunk: Map, + cost: SFLCost, +): void { + let done = false; + while (!done) { + done = mergeStepDown(chunkId, deps, chunks, txToChunk, cost) === null; + } +} + +function buildChunkAdjacency( + deps: SFLDependency[], + chunkTxs: Set, + excludeDep?: SFLDependency +): Map> { + const adj = new Map>(); + for (const tx of chunkTxs) { + adj.set(tx, new Set()); + } + for (const d of deps) { + if (d !== excludeDep && d.active && chunkTxs.has(d.parent) && chunkTxs.has(d.child)) { + const parentAdj = adj.get(d.parent); + const childAdj = adj.get(d.child); + if (parentAdj) { + parentAdj.add(d.child); + } + if (childAdj) { + childAdj.add(d.parent); + } + } + } + return adj; +} + +function bfsReachable(adj: Map>, start: ClusterTx): Set { + const visited = new Set(); + const queue: ClusterTx[] = [start]; + visited.add(start); + while (queue.length > 0) { + const node = queue.shift(); + if (node) { + const neighbors = adj.get(node); + if (neighbors) { + for (const neighbor of neighbors) { + if (!visited.has(neighbor)) { + visited.add(neighbor); + queue.push(neighbor); + } + } + } + } + } + return visited; +} + +function optimizeSFL( + deps: SFLDependency[], + chunks: Map, + txToChunk: Map, + nextChunkId: number, + maxCost: number, + cost: SFLCost, +): number { + const queue: number[] = []; + const onQueue = new Set(); + for (const [chunkId] of chunks) { + queue.push(chunkId); + const j = Math.floor(Math.random() * queue.length); + if (j !== queue.length - 1) { + [queue[queue.length - 1], queue[j]] = [queue[j], queue[queue.length - 1]]; + } + onQueue.add(chunkId); + } + + cost.cost += 13 * chunks.size; + + while (cost.cost < maxCost) { + let chunkId: number | undefined; + let numPopped = 0; + while (queue.length > 0) { + const candidate = queue.shift(); + if (candidate === undefined) { + break; + } + numPopped++; + onQueue.delete(candidate); + if (chunks.has(candidate)) { + chunkId = candidate; + break; + } + } + cost.cost += 1 * numPopped + 4; + if (chunkId === undefined) { + break; + } + + const chunk = chunks.get(chunkId); + if (!chunk) { + break; + } + + const split = pickDependencyToSplit(deps, chunk, chunkId, txToChunk, cost); + if (split) { + const result = splitAndMerge( + split.dep, split.parentSide, chunkId, chunk, + deps, chunks, txToChunk, nextChunkId, cost, + ); + nextChunkId = result.nextChunkId; + + if (!onQueue.has(chunkId) && chunks.has(chunkId)) { + onQueue.add(chunkId); + queue.push(chunkId); + } + if (!onQueue.has(result.childChunkId) && chunks.has(result.childChunkId)) { + onQueue.add(result.childChunkId); + queue.push(result.childChunkId); + } + } + } + + return nextChunkId; +} + +function computeDepTopSet( + dep: SFLDependency, + chunk: SFLChunk, + chunkId: number, + deps: SFLDependency[], + txToChunk: Map, +): { parentSide: Set; topFee: number; topWeight: number } | null { + if (!dep.active + || txToChunk.get(dep.parent) !== chunkId + || txToChunk.get(dep.child) !== chunkId) { + return null; + } + const adj = buildChunkAdjacency(deps, chunk.txs, dep); + const parentSide = bfsReachable(adj, dep.parent); + if (parentSide.has(dep.child)) { + return null; + } + let topFee = 0; + let topWeight = 0; + for (const tx of parentSide) { + topFee += tx.effectiveFee; + topWeight += tx.weight; + } + return { parentSide, topFee, topWeight }; +} + +function pickDependencyToSplit( + deps: SFLDependency[], + chunk: SFLChunk, + chunkId: number, + txToChunk: Map, + cost: SFLCost, +): { dep: SFLDependency; parentSide: Set } | null { + let best: { dep: SFLDependency; parentSide: Set } | null = null; + let bestTiebreak = 0; + + for (const dep of deps) { + const split = computeDepTopSet(dep, chunk, chunkId, deps, txToChunk); + if (split && split.topFee * chunk.weight > chunk.fee * split.topWeight) { + const tiebreak = Math.random(); + if (tiebreak >= bestTiebreak) { + bestTiebreak = tiebreak; + best = { dep, parentSide: split.parentSide }; + } + } + } + + cost.cost += 8 * chunk.txs.size + 9; + return best; +} + +function splitAndMerge( + dep: SFLDependency, + parentSide: Set, + parentChunkId: number, + parentChunk: SFLChunk, + deps: SFLDependency[], + chunks: Map, + txToChunk: Map, + nextChunkId: number, + cost: SFLCost, +): { nextChunkId: number; childChunkId: number } { + dep.active = false; + + const origChunkSize = parentChunk.txs.size; + + const childSide = new Set(); + for (const tx of parentChunk.txs) { + if (!parentSide.has(tx)) { + childSide.add(tx); + } + } + + let childFee = 0; + let childWeight = 0; + for (const tx of childSide) { + childFee += tx.effectiveFee; + childWeight += tx.weight; + } + + const childChunkId = nextChunkId++; + chunks.set(childChunkId, { + id: childChunkId, + txs: childSide, + fee: childFee, + weight: childWeight, + }); + + let parentFee = 0; + let parentWeight = 0; + for (const tx of parentSide) { + parentFee += tx.effectiveFee; + parentWeight += tx.weight; + } + parentChunk.txs = parentSide; + parentChunk.fee = parentFee; + parentChunk.weight = parentWeight; + + for (const tx of childSide) { + txToChunk.set(tx, childChunkId); + } + + for (const d of deps) { + if (d.active && txToChunk.get(d.parent) !== txToChunk.get(d.child)) { + d.active = false; + } + } + + cost.cost += 11 * (origChunkSize - 1) + 8; + + let needsSelfMerge = false; + for (const d of deps) { + if (!d.active && txToChunk.get(d.parent) === parentChunkId && txToChunk.get(d.child) === childChunkId) { + needsSelfMerge = true; + break; + } + } + + if (needsSelfMerge) { + const selfDep = pickRandomCrossChunkDep(childChunkId, parentChunkId, deps, txToChunk, chunks, cost); + if (selfDep) { + selfDep.active = true; + mergeChunks(childChunkId, parentChunkId, chunks, txToChunk); + activateInternalDeps(childChunkId, deps, txToChunk, cost, chunks); + } + } else { + mergeUpwards(parentChunkId, deps, chunks, txToChunk, cost); + mergeDownwards(childChunkId, deps, chunks, txToChunk, cost); + } + + return { nextChunkId, childChunkId }; +} + +interface MinimizeQueueEntry { + chunkId: number; + pivot: ClusterTx; + movePivotDown: boolean; + secondStage: boolean; +} + +function minimizeSFL( + deps: SFLDependency[], + chunks: Map, + txToChunk: Map, + nextChunkId: number, + maxCost: number, + cost: SFLCost, +): number { + const queue: MinimizeQueueEntry[] = []; + + for (const [chunkId, chunk] of chunks) { + const pivot = pickRandomTx(chunk.txs); + if (pivot) { + queue.push({ chunkId, pivot, movePivotDown: Math.random() < 0.5, secondStage: false }); + const j = Math.floor(Math.random() * queue.length); + if (j !== queue.length - 1) { + [queue[queue.length - 1], queue[j]] = [queue[j], queue[queue.length - 1]]; + } + } + } + + cost.cost += 18 * chunks.size; + + while (queue.length > 0 && cost.cost < maxCost) { + const entry = queue.shift(); + if (!entry) { + break; + } + + const chunk = chunks.get(entry.chunkId); + if (chunk) { + nextChunkId = minimizeChunkStep( + chunk, entry, + deps, chunks, txToChunk, nextChunkId, queue, cost, + ); + } + } + + return nextChunkId; +} + +function minimizeChunkStep( + chunk: SFLChunk, + entry: MinimizeQueueEntry, + deps: SFLDependency[], + chunks: Map, + txToChunk: Map, + nextChunkId: number, + queue: MinimizeQueueEntry[], + cost: SFLCost, +): number { + const { chunkId, pivot, movePivotDown, secondStage } = entry; + + let haveAny = false; + let bestDep: SFLDependency | null = null; + let bestParentSide: Set | null = null; + let bestTiebreak = 0; + + for (const dep of deps) { + const split = computeDepTopSet(dep, chunk, chunkId, deps, txToChunk); + if (split && split.topFee * chunk.weight === chunk.fee * split.topWeight) { + haveAny = true; + if (movePivotDown !== split.parentSide.has(pivot)) { + const tiebreak = Math.random(); + if (tiebreak > bestTiebreak) { + bestTiebreak = tiebreak; + bestDep = dep; + bestParentSide = split.parentSide; + } + } + } + } + + cost.cost += 11 * chunk.txs.size + 11; + + if (!haveAny) { + cost.cost += 7; + return nextChunkId; + } + + if (!bestDep || !bestParentSide) { + if (!secondStage) { + queue.push({ chunkId, pivot, movePivotDown: !movePivotDown, secondStage: true }); + } + cost.cost += 7; + return nextChunkId; + } + + const result = splitAndMerge( + bestDep, bestParentSide, chunkId, chunk, + deps, chunks, txToChunk, nextChunkId, cost, + ); + nextChunkId = result.nextChunkId; + const childChunkId = result.childChunkId; + + cost.cost += 17 + 7; + + if (movePivotDown) { + const parentPivot = pickRandomTx(chunks.get(chunkId)?.txs ?? new Set()); + if (parentPivot) { + queue.push({ chunkId, pivot: parentPivot, movePivotDown: Math.random() < 0.5, secondStage: false }); + } + queue.push({ chunkId: childChunkId, pivot, movePivotDown, secondStage }); + } else { + queue.push({ chunkId, pivot, movePivotDown, secondStage }); + const childPivot = pickRandomTx(chunks.get(childChunkId)?.txs ?? new Set()); + if (childPivot) { + queue.push({ chunkId: childChunkId, pivot: childPivot, movePivotDown: Math.random() < 0.5, secondStage: false }); + } + } + + if (queue.length >= 2 && Math.random() < 0.5) { + const last = queue.length - 1; + [queue[last], queue[last - 1]] = [queue[last - 1], queue[last]]; + } + + return nextChunkId; +} + +function chunkCmp(a: SFLChunk, b: SFLChunk, chunkMaxOrder: Map): number { + if (higherFeerate(a.fee, a.weight, b.fee, b.weight)) { + return -1; + } + if (higherFeerate(b.fee, b.weight, a.fee, a.weight)) { + return 1; + } + if (a.weight !== b.weight) { + return b.weight - a.weight; + } + return (chunkMaxOrder.get(a.id) ?? 0) - (chunkMaxOrder.get(b.id) ?? 0); +} + +function txCmp(a: ClusterTx, b: ClusterTx): number { + if (higherFeerate(a.effectiveFee, a.weight, b.effectiveFee, b.weight)) { + return -1; + } + if (higherFeerate(b.effectiveFee, b.weight, a.effectiveFee, a.weight)) { + return 1; + } + if (a.weight !== b.weight) { + return b.weight - a.weight; + } + return a.order - b.order; +} + +function extractLinearization( + chunks: Map, + txToChunk: Map, +): ClusterTx[] { + const chunkList = [...chunks.values()]; + + const chunkMaxOrder = new Map(); + for (const c of chunkList) { + let max = 0; + for (const tx of c.txs) { + if (tx.order > max) { + max = tx.order; + } + } + chunkMaxOrder.set(c.id, max); + } + + const { chunkDeps, chunkChildren } = buildChunkDependencies(chunkList, txToChunk); + + return emitLinearization(chunkList, chunkDeps, chunkChildren, chunkMaxOrder, chunks); +} + +function buildChunkDependencies( + chunkList: SFLChunk[], + txToChunk: Map +): { chunkDeps: Map; chunkChildren: Map } { + const chunkDeps = new Map(); + const chunkChildren = new Map(); + + for (const c of chunkList) { + chunkChildren.set(c.id, []); + } + + for (const c of chunkList) { + const depChunks = new Set(); + for (const tx of c.txs) { + for (const parent of tx.parents) { + const parentChunk = txToChunk.get(parent); + if (parentChunk !== undefined && parentChunk !== c.id) { + depChunks.add(parentChunk); + } + } + } + chunkDeps.set(c.id, depChunks.size); + for (const d of depChunks) { + const children = chunkChildren.get(d); + if (children) { + children.push(c.id); + } + } + } + + return { chunkDeps, chunkChildren }; +} + +function emitLinearization( + chunkList: SFLChunk[], + chunkDeps: Map, + chunkChildren: Map, + chunkMaxOrder: Map, + chunkMap: Map +): ClusterTx[] { + const result: ClusterTx[] = []; + + const readyChunks: SFLChunk[] = []; + for (const c of chunkList) { + if (chunkDeps.get(c.id) === 0) { + readyChunks.push(c); + } + } + readyChunks.sort((a, b) => chunkCmp(a, b, chunkMaxOrder)); + + while (readyChunks.length > 0) { + const chunk = readyChunks.shift(); + if (!chunk) { + break; + } + + emitChunkTxs(chunk, result); + + const children = chunkChildren.get(chunk.id); + if (children) { + for (const childChunkId of children) { + const prevCount = chunkDeps.get(childChunkId) ?? 0; + const newCount = prevCount - 1; + chunkDeps.set(childChunkId, newCount); + if (newCount === 0) { + const childChunk = chunkMap.get(childChunkId); + if (childChunk) { + insertSortedChunk(readyChunks, childChunk, chunkMaxOrder); + } + } + } + } + } + + return result; +} + +function emitChunkTxs( + chunk: SFLChunk, + result: ClusterTx[] +): void { + const txSet = new Set(chunk.txs); + const txDepCount = new Map(); + for (const tx of txSet) { + let count = 0; + for (const parent of tx.parents) { + if (txSet.has(parent)) { + count++; + } + } + txDepCount.set(tx, count); + } + + const readyTxs: ClusterTx[] = []; + for (const tx of txSet) { + if (txDepCount.get(tx) === 0) { + readyTxs.push(tx); + } + } + readyTxs.sort((a, b) => txCmp(a, b)); + + const emitted = new Set(); + while (readyTxs.length > 0) { + const best = readyTxs.shift(); + if (!best) { + break; + } + result.push(best); + emitted.add(best); + + for (const child of best.children) { + if (txSet.has(child) && !emitted.has(child)) { + const prevCount = txDepCount.get(child) ?? 0; + const newCount = prevCount - 1; + txDepCount.set(child, newCount); + if (newCount === 0) { + insertSortedTx(readyTxs, child); + } + } + } + } +} + +function insertSortedChunk(arr: SFLChunk[], item: SFLChunk, chunkMaxOrder: Map): void { + const idx = arr.findIndex(e => chunkCmp(item, e, chunkMaxOrder) < 0); + if (idx === -1) { + arr.push(item); + } else { + arr.splice(idx, 0, item); + } +} + +function insertSortedTx(arr: ClusterTx[], item: ClusterTx): void { + const idx = arr.findIndex(e => txCmp(item, e) < 0); + if (idx === -1) { + arr.push(item); + } else { + arr.splice(idx, 0, item); + } +} + +function minimizeChunks(chunks: LinearizationChunk[]): LinearizationChunk[] { + const result: LinearizationChunk[] = []; + + for (const chunk of chunks) { + if (chunk.txs.length <= 1) { + result.push(chunk); + } else { + const subChunks = splitChunkByComponents(chunk); + result.push(...subChunks); + } + } + + return result; +} + +function splitChunkByComponents(chunk: LinearizationChunk): LinearizationChunk[] { + const txSet = new Set(chunk.txs); + const visited = new Set(); + const components: ClusterTx[][] = []; + + for (const tx of chunk.txs) { + if (!visited.has(tx)) { + const component = bfsComponentWithinChunk(tx, txSet, visited); + components.push(component); + } + } + + if (components.length <= 1) { + return [chunk]; + } + + const posInLin = new Map(); + for (let i = 0; i < chunk.txs.length; i++) { + posInLin.set(chunk.txs[i], i); + } + + components.sort((a, b) => { + let aFee = 0, aWeight = 0; + for (const t of a) { aFee += t.effectiveFee; aWeight += t.weight; } + let bFee = 0, bWeight = 0; + for (const t of b) { bFee += t.effectiveFee; bWeight += t.weight; } + if (higherFeerate(aFee, aWeight, bFee, bWeight)) { return -1; } + if (higherFeerate(bFee, bWeight, aFee, aWeight)) { return 1; } + const aMin = Math.min(...a.map(t => posInLin.get(t) ?? 0)); + const bMin = Math.min(...b.map(t => posInLin.get(t) ?? 0)); + return aMin - bMin; + }); + + return components.map(comp => { + comp.sort((a, b) => (posInLin.get(a) ?? 0) - (posInLin.get(b) ?? 0)); + + let fee = 0; + let weight = 0; + for (const tx of comp) { + fee += tx.effectiveFee; + weight += tx.weight; + } + return { txs: comp, fee, weight }; + }); +} + +function bfsComponentWithinChunk( + start: ClusterTx, + txSet: Set, + visited: Set +): ClusterTx[] { + const component: ClusterTx[] = []; + const queue: ClusterTx[] = [start]; + visited.add(start); + + while (queue.length > 0) { + const node = queue.shift(); + if (!node) { + break; + } + component.push(node); + for (const parent of node.parents) { + if (txSet.has(parent) && !visited.has(parent)) { + visited.add(parent); + queue.push(parent); + } + } + for (const child of node.children) { + if (txSet.has(child) && !visited.has(child)) { + visited.add(child); + queue.push(child); + } + } + } + + return component; +} + +export function linearizeCluster( + txs: Set, + costBudget: number, + existingLinearization?: ClusterTx[], +): { linearization: ClusterTx[]; chunks: LinearizationChunk[] } { + let linearization = spanningForestLinearize(txs, costBudget, existingLinearization); + linearization = postLinearize(linearization); + let chunks = chunkify(linearization); + chunks = minimizeChunks(chunks); + linearization = chunks.flatMap(c => c.txs); + chunks = chunkify(linearization); + chunks = canonicalizeChunkOrder(chunks); + linearization = chunks.flatMap(c => c.txs); + return { linearization, chunks }; +} + +function canonicalizeChunkOrder(chunks: LinearizationChunk[]): LinearizationChunk[] { + if (chunks.length <= 1) { + return chunks; + } + + const txToChunkIdx = new Map(); + for (let i = 0; i < chunks.length; i++) { + for (const tx of chunks[i].txs) { + txToChunkIdx.set(tx, i); + } + } + + const { depCount, chunkChildren } = buildCanonicalChunkDeps(chunks, txToChunkIdx); + const maxOrder = computeChunkMaxOrder(chunks); + + const ready: number[] = []; + for (let i = 0; i < chunks.length; i++) { + if (depCount[i] === 0) { + ready.push(i); + } + } + ready.sort((a, b) => canonicalChunkCmp(chunks, maxOrder, a, b)); + + const result: LinearizationChunk[] = []; + while (ready.length > 0) { + const idx = ready.shift(); + if (idx === undefined) { + break; + } + result.push(chunks[idx]); + + for (const childIdx of chunkChildren[idx]) { + depCount[childIdx]--; + if (depCount[childIdx] === 0) { + insertSortedCanonicalChunk(ready, childIdx, chunks, maxOrder); + } + } + } + + return result; +} + +function buildCanonicalChunkDeps( + chunks: LinearizationChunk[], + txToChunkIdx: Map, +): { depCount: number[]; chunkChildren: number[][] } { + const depCount = new Array(chunks.length).fill(0); + const chunkChildren: number[][] = chunks.map(() => []); + const seen: Set[] = chunks.map(() => new Set()); + + for (let i = 0; i < chunks.length; i++) { + for (const tx of chunks[i].txs) { + for (const parent of tx.parents) { + const parentIdx = txToChunkIdx.get(parent); + if (parentIdx !== undefined && parentIdx !== i && !seen[i].has(parentIdx)) { + seen[i].add(parentIdx); + depCount[i]++; + chunkChildren[parentIdx].push(i); + } + } + } + } + + return { depCount, chunkChildren }; +} + +function computeChunkMaxOrder(chunks: LinearizationChunk[]): number[] { + return chunks.map(chunk => { + let max = 0; + for (const tx of chunk.txs) { + if (tx.order > max) { + max = tx.order; + } + } + return max; + }); +} + +function canonicalChunkCmp(chunks: LinearizationChunk[], maxOrder: number[], a: number, b: number): number { + const ac = chunks[a]; + const bc = chunks[b]; + if (higherFeerate(ac.fee, ac.weight, bc.fee, bc.weight)) { + return -1; + } + if (higherFeerate(bc.fee, bc.weight, ac.fee, ac.weight)) { + return 1; + } + if (ac.weight !== bc.weight) { + return ac.weight - bc.weight; + } + return maxOrder[a] - maxOrder[b]; +} + +function insertSortedCanonicalChunk(ready: number[], idx: number, chunks: LinearizationChunk[], maxOrder: number[]): void { + const insertPos = ready.findIndex(r => canonicalChunkCmp(chunks, maxOrder, idx, r) < 0); + if (insertPos === -1) { + ready.push(idx); + } else { + ready.splice(insertPos, 0, idx); + } +} diff --git a/backend/src/config.ts b/backend/src/config.ts index 3fe3db2ee..6ac08a2f1 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -6,7 +6,7 @@ interface IConfig { MEMPOOL: { ENABLED: boolean; OFFICIAL: boolean; - NETWORK: 'mainnet' | 'testnet' | 'signet' | 'liquid' | 'liquidtestnet'; + NETWORK: 'mainnet' | 'testnet' | 'testnet4' | 'signet' | 'liquid' | 'liquidtestnet' | 'regtest'; BACKEND: 'esplora' | 'electrum' | 'none'; HTTP_PORT: number; UNIX_SOCKET_PATH: string; @@ -34,6 +34,8 @@ interface IConfig { POOLS_JSON_TREE_URL: string, POOLS_UPDATE_DELAY: number, AUDIT: boolean; + CLUSTER_MEMPOOL: boolean; + CLUSTER_MEMPOOL_INDEXING: boolean; RUST_GBT: boolean; LIMIT_GBT: boolean; CPFP_INDEXING: boolean; @@ -205,6 +207,8 @@ const defaults: IConfig = { 'POOLS_JSON_TREE_URL': 'https://api.github.com/repos/mempool/mining-pools/git/trees/master', 'POOLS_UPDATE_DELAY': 604800, // in seconds, default is one week 'AUDIT': false, + 'CLUSTER_MEMPOOL': false, + 'CLUSTER_MEMPOOL_INDEXING': false, 'RUST_GBT': true, 'LIMIT_GBT': false, 'CPFP_INDEXING': false, @@ -398,7 +402,7 @@ class Config implements IConfig { }); return next; }); - } + }; } export default new Config(); diff --git a/backend/src/database.ts b/backend/src/database.ts index 818878a68..6e853c6e1 100644 --- a/backend/src/database.ts +++ b/backend/src/database.ts @@ -25,6 +25,7 @@ import { execSync } from 'child_process'; timezone: '+00:00', }; + /** @asyncUnsafe */ private checkDBFlag() { if (config.DATABASE.ENABLED === false) { const stack = new Error().stack; @@ -32,6 +33,7 @@ import { execSync } from 'child_process'; } } + /** @asyncUnsafe */ public async query(query, params?, errorLogLevel: LogLevel | 'silent' = 'debug', connection?: PoolConnection): Promise<[T, FieldPacket[]]> { @@ -76,6 +78,7 @@ import { execSync } from 'child_process'; } } + /** @asyncSafe */ private async $rollbackAtomic(connection: PoolConnection): Promise { try { await connection.rollback(); @@ -85,6 +88,7 @@ import { execSync } from 'child_process'; } } + /** @asyncSafe */ public async $atomicQuery(queries: { query, params }[], errorLogLevel: LogLevel | 'silent' = 'debug'): Promise<[T, FieldPacket[]][]> { @@ -116,6 +120,8 @@ import { execSync } from 'child_process'; } } + + /** @asyncSafe */ public async checkDbConnection() { this.checkDBFlag(); try { @@ -171,10 +177,12 @@ import { execSync } from 'child_process'; } } + /** @asyncSafe */ private async getPool(): Promise { if (this.pool === null) { this.pool = createPool(this.poolConfig); this.pool.on('connection', function (newConnection: PoolConnection) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises -- callback API, not a promise despite types newConnection.query(`SET time_zone='+00:00'`); }); } @@ -188,7 +196,11 @@ import { execSync } from 'child_process'; */ public async close(): Promise { if (this.pool !== null) { - await this.pool.end(); + try { + await this.pool.end(); + } catch (e) { + logger.err(`Exception in close. Reason: ${(e instanceof Error ? e.message : e)}`); + } this.pool = null; logger.debug('Database connection pool closed'); } diff --git a/backend/src/index.ts b/backend/src/index.ts index 249d08056..04690c4bc 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,5 +1,6 @@ import express from 'express'; import { Application, Request, Response, NextFunction } from 'express'; +import * as fs from 'fs'; import * as http from 'http'; import * as WebSocket from 'ws'; import bitcoinApi from './api/bitcoin/bitcoin-api-factory'; @@ -67,8 +68,12 @@ class Server { constructor() { this.app = express(); + if (cluster.isPrimary && config.MEMPOOL.UNIX_SOCKET_PATH) { + this.clearStaleUnixSocket(config.MEMPOOL.UNIX_SOCKET_PATH); + } + if (!config.MEMPOOL.SPAWN_CLUSTER_PROCS) { - this.startServer(); + void this.startServer(); return; } @@ -92,10 +97,11 @@ class Server { }, 10000); }); } else { - this.startServer(true); + void this.startServer(true); } } + /** @asyncSafe */ async startServer(worker = false): Promise { logger.notice(`Starting Mempool Server${worker ? ' (worker)' : ''}... (${backendInfo.getShortCommitHash()})`); @@ -148,33 +154,42 @@ class Server { ; if (config.DATABASE.ENABLED && config.FIAT_PRICE.ENABLED) { + /** @asyncUnsafe */ await priceUpdater.$initializeLatestPriceWithDb(); } this.server = http.createServer(this.app); - this.wss = new WebSocket.Server({ server: this.server }); + this.wss = new WebSocket.Server({ server: this.server, maxPayload: websocketHandler.MAX_MESSAGE_SIZE }); if (config.MEMPOOL.UNIX_SOCKET_PATH) { this.serverUnixSocket = http.createServer(this.app); - this.wssUnixSocket = new WebSocket.Server({ server: this.serverUnixSocket }); + this.wssUnixSocket = new WebSocket.Server({ server: this.serverUnixSocket, maxPayload: websocketHandler.MAX_MESSAGE_SIZE }); } this.setUpWebsocketHandling(); await poolsUpdater.updatePoolsJson(); // Needs to be done before loading the disk cache because we sometimes wipe it - if (config.DATABASE.ENABLED === true && config.MEMPOOL.ENABLED && ['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK) && !poolsUpdater.currentSha) { - logger.err(`Failed to retreive pools-v2.json sha, cannot run block indexing. Please make sure you've set valid urls in your mempool-config.json::MEMPOOL::POOLS_JSON_URL and mempool-config.json::MEMPOOL::POOLS_JSON_TREE_UR, aborting now`); + if (config.DATABASE.ENABLED === true && config.MEMPOOL.ENABLED && ['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK) && !poolsUpdater.currentSha) { + logger.err(`Failed to retrieve pools-v2.json sha, cannot run block indexing. Please make sure you've set valid urls in your mempool-config.json::MEMPOOL::POOLS_JSON_URL and mempool-config.json::MEMPOOL::POOLS_JSON_TREE_UR, aborting now`); return process.exit(1); } await syncAssets.syncAssets$(); if (config.DATABASE.ENABLED) { + /** @asyncUnsafe */ await mempoolBlocks.updatePools$(); } if (config.MEMPOOL.ENABLED) { if (config.MEMPOOL.CACHE_ENABLED) { await diskCache.$loadMempoolCache(); } else if (config.REDIS.ENABLED) { - await redisCache.$loadCache(); + let currentMempoolTxids: Set | undefined; + try { + currentMempoolTxids = new Set(await bitcoinApi.$getRawMempool()); + } catch (e) { + logger.warn(`Failed to fetch raw mempool before loading Redis cache. Reason: ${e instanceof Error ? e.message : e}`); + } + /** @asyncUnsafe */ + await redisCache.$loadCache(currentMempoolTxids); } } @@ -197,20 +212,20 @@ class Server { } if (config.FIAT_PRICE.ENABLED) { - priceUpdater.$run(); + void priceUpdater.$run(); } await chainTips.updateOrphanedBlocks(); this.setUpHttpApiRoutes(); if (config.MEMPOOL.ENABLED) { - this.runMainUpdateLoop(); + void this.runMainUpdateLoop(); } setInterval(() => { this.healthCheck(); }, 2500); if (config.LIGHTNING.ENABLED) { - this.$runLightningBackend(); + void this.$runLightningBackend(); } this.server.listen(config.MEMPOOL.HTTP_PORT, () => { @@ -220,6 +235,8 @@ class Server { logger.notice(`Mempool Server is running on port ${config.MEMPOOL.HTTP_PORT}`); } }); + this.server.keepAliveTimeout = 70 * 1000; + this.server.headersTimeout = 71 * 1000; if (this.serverUnixSocket) { this.serverUnixSocket.listen(config.MEMPOOL.UNIX_SOCKET_PATH, () => { @@ -229,11 +246,26 @@ class Server { logger.notice(`Mempool Server is listening on ${config.MEMPOOL.UNIX_SOCKET_PATH}`); } }); + + this.serverUnixSocket.keepAliveTimeout = 70 * 1000; + this.serverUnixSocket.headersTimeout = 71 * 1000; } - poolsUpdater.$startService(); + void poolsUpdater.$startService(); } + clearStaleUnixSocket(path: string): void { + try { + if (fs.existsSync(path)) { + fs.unlinkSync(path); + logger.notice(`Removed stale unix socket ${path}`); + } + } catch (e) { + logger.err(`Failed to remove stale unix socket ${path}. Reason: ${e instanceof Error ? e.message : e}`); + } + } + + /** @asyncSafe */ async runMainUpdateLoop(): Promise { const start = Date.now(); try { @@ -256,13 +288,13 @@ class Server { if (numHandledBlocks === 0) { await memPool.$updateMempool(newMempool, latestAccelerations, minFeeMempool, minFeeTip, pollRate); } - indexer.$run(); + void indexer.$run(); if (config.WALLETS.ENABLED) { // might take a while, so run in the background - walletApi.$syncWallets(); + void walletApi.$syncWallets(); } if (config.FIAT_PRICE.ENABLED) { - priceUpdater.$run(); + void priceUpdater.$run(); } // rerun immediately if we skipped the mempool update, otherwise wait POLL_RATE_MS @@ -294,6 +326,7 @@ class Server { } } + /** @asyncSafe */ async $runLightningBackend(): Promise { try { await fundingTxFetcher.$init(); @@ -303,7 +336,7 @@ class Server { } catch(e) { logger.err(`Exception in $runLightningBackend. Restarting in 1 minute. Reason: ${(e instanceof Error ? e.message : e)}`); await Common.sleep$(1000 * 60); - this.$runLightningBackend(); + void this.$runLightningBackend(); }; } @@ -319,7 +352,6 @@ class Server { blocks.setNewBlockCallback(async () => { try { await elementsParser.$parse(); - await elementsParser.$updateFederationUtxos(); } catch (e) { logger.warn('Elements parsing error: ' + (e instanceof Error ? e.message : e)); } @@ -329,16 +361,15 @@ class Server { if (config.MEMPOOL.ENABLED) { statistics.setNewStatisticsEntryCallback(websocketHandler.handleNewStatistic.bind(websocketHandler)); memPool.setAsyncMempoolChangedCallback(websocketHandler.$handleMempoolChange.bind(websocketHandler)); - blocks.setNewAsyncBlockCallback(websocketHandler.handleNewBlock.bind(websocketHandler)); } if (config.FIAT_PRICE.ENABLED) { priceUpdater.setRatesChangedCallback(websocketHandler.handleNewConversionRates.bind(websocketHandler)); } loadingIndicators.setProgressChangedCallback(websocketHandler.handleLoadingChanged.bind(websocketHandler)); - accelerationApi.connectWebsocket(); + void accelerationApi.connectWebsocket(); if (config.STRATUM.ENABLED) { - stratumApi.connectWebsocket(); + void stratumApi.connectWebsocket(); } } @@ -390,6 +421,16 @@ class Server { this.warnedHeapCritical = false; this.maxHeapSize = 0; this.lastHeapLogTime = now; + this.server?.getConnections((error, count) => { + if (!error) { + logger.debug(`${count} open TCP sockets`); + } + }); + this.serverUnixSocket?.getConnections((error, count) => { + if (!error) { + logger.debug(`${count} open unix sockets`); + } + }); } } diff --git a/backend/src/indexer.ts b/backend/src/indexer.ts index b3bfd2521..9d47de2f2 100644 --- a/backend/src/indexer.ts +++ b/backend/src/indexer.ts @@ -26,6 +26,7 @@ class Indexer { private indexerRunning = false; private tasksRunning: { [key in TaskName]?: boolean; } = {}; private tasksScheduled: { [key in TaskName]?: NodeJS.Timeout; } = {}; + private reindexTimeout: NodeJS.Timeout | undefined; private coreIndexes: CoreIndex[] = []; public indexerIsRunning(): boolean { @@ -34,6 +35,8 @@ class Indexer { /** * Check which core index is available for indexing + * + * @asyncUnsafe */ public async checkAvailableCoreIndexes(): Promise { const updatedCoreIndexes: CoreIndex[] = []; @@ -45,13 +48,13 @@ class Indexer { synced: indexes[indexName].synced, best_block_height: indexes[indexName].best_block_height, }; - logger.info(`Core index '${indexName}' is ${indexes[indexName].synced ? 'synced' : 'not synced'}. Best block height is ${indexes[indexName].best_block_height}`); + logger.info(`Core index '${indexName}' is ${indexes[indexName].synced ? 'synced' : 'not synced'}. Best block height is ${indexes[indexName].best_block_height}`); updatedCoreIndexes.push(newState); if (indexName === 'coinstatsindex' && newState.synced === true) { const previousState = this.isCoreIndexReady('coinstatsindex'); // if (!previousState || previousState.synced === false) { - this.runSingleTask('coinStatsIndex'); + void this.runSingleTask('coinStatsIndex'); // } } } @@ -61,9 +64,9 @@ class Indexer { /** * Return the best block height if a core index is available, or 0 if not - * - * @param name - * @returns + * + * @param name + * @returns */ public isCoreIndexReady(name: string): CoreIndex | null { for (const index of this.coreIndexes) { @@ -76,10 +79,23 @@ class Indexer { public reindex(): void { if (Common.indexingEnabled()) { + if (this.reindexTimeout) { + clearTimeout(this.reindexTimeout); + this.reindexTimeout = undefined; + } this.runIndexer = true; } } + private scheduleNextRun(timeout: number): void { + if (!this.reindexTimeout) { // Only one future run should be planned, ignore if already scheduled + this.reindexTimeout = setTimeout(() => { + this.reindexTimeout = undefined; + this.reindex(); + }, timeout); + } + } + /** * schedules a single task to run in `timeout` ms * only one task of each type may be scheduled @@ -97,12 +113,11 @@ class Indexer { } } this.tasksScheduled[task] = setTimeout(async () => { + delete this.tasksScheduled[task]; try { await this.runSingleTask(task); } catch (e) { logger.err(`Unexpected error in scheduled task ${task}: ` + (e instanceof Error ? e.message : e)); - } finally { - clearTimeout(this.tasksScheduled[task]); } }, timeout); } @@ -111,6 +126,8 @@ class Indexer { * Runs a single task immediately * * (use `scheduleSingleTask` instead to queue a task to run after some timeout) + * + * @asyncSafe */ public async runSingleTask(task: TaskName): Promise { if (!Common.indexingEnabled() || this.tasksRunning[task]) { @@ -120,13 +137,14 @@ class Indexer { switch (task) { case 'blocksPrices': { - if (!['testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK) && config.FIAT_PRICE.ENABLED) { - let lastestPriceId; + if (!['testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK) && config.FIAT_PRICE.ENABLED) { + let latestPriceId; try { - lastestPriceId = await PricesRepository.$getLatestPriceId(); + latestPriceId = await PricesRepository.$getLatestPriceId(); } catch (e) { logger.debug('failed to fetch latest price id from db: ' + (e instanceof Error ? e.message : e)); - } if (priceUpdater.historyInserted === false || lastestPriceId === null) { + } + if (priceUpdater.historyInserted === false || latestPriceId === null) { logger.debug(`Blocks prices indexer is waiting for the price updater to complete`, logger.tags.mining); this.scheduleSingleTask(task, 10000); } else { @@ -149,6 +167,7 @@ class Indexer { this.tasksRunning[task] = false; } + /** @asyncSafe */ public async $run(): Promise { if (!Common.indexingEnabled() || this.runIndexer === false || this.indexerRunning === true || mempool.hasPriority() @@ -156,38 +175,44 @@ class Indexer { return; } - if (config.FIAT_PRICE.ENABLED) { - try { - await priceUpdater.$run(); - } catch (e) { - logger.err(`Running priceUpdater failed. Reason: ` + (e instanceof Error ? e.message : e)); - } - } - - // Do not attempt to index anything unless Bitcoin Core is fully synced - const blockchainInfo = await bitcoinClient.getBlockchainInfo(); - if (blockchainInfo.blocks !== blockchainInfo.headers) { - return; - } - this.runIndexer = false; this.indexerRunning = true; - logger.debug(`Running mining indexer`); - - await this.checkAvailableCoreIndexes(); + const retryDelay = 10000; + const runEvery = 1000 * 3600; // 1 hour + let nextRunDelay = runEvery; + let runSuccessful = false; try { + if (config.FIAT_PRICE.ENABLED) { + try { + await priceUpdater.$run(); + } catch (e) { + logger.err(`Running priceUpdater failed. Reason: ` + (e instanceof Error ? e.message : e)); + } + } + + // Do not attempt to index anything unless Bitcoin Core is fully synced + const blockchainInfo = await bitcoinClient.getBlockchainInfo(); + if (blockchainInfo.blocks !== blockchainInfo.headers) { + logger.debug(`Bitcoin Core not fully synced, retrying index run in 10 seconds.`); + nextRunDelay = retryDelay; + return; + } + + logger.debug(`Running mining indexer`); + + await this.checkAvailableCoreIndexes(); + const chainValid = await blocks.$generateBlockDatabase(); if (chainValid === false) { // Chain of block hash was invalid, so we need to reindex. Stop here and continue at the next iteration logger.warn(`The chain of block hash is invalid, re-indexing invalid data in 10 seconds.`, logger.tags.mining); - setTimeout(() => this.reindex(), 10000); - this.indexerRunning = false; + nextRunDelay = retryDelay; return; } - this.runSingleTask('blocksPrices'); + void this.runSingleTask('blocksPrices'); await blocks.$indexCoinbaseAddresses(); await mining.$indexDifficultyAdjustments(); await mining.$generateNetworkHashrateHistory(); @@ -195,26 +220,30 @@ class Indexer { await blocks.$generateBlocksSummariesDatabase(); await blocks.$generateCPFPDatabase(); await blocks.$generateAuditStats(); + await blocks.$indexBlocksFirstSeen(); await auditReplicator.$sync(); await statisticsReplicator.$sync(); await AccelerationRepository.$indexPastAccelerations(); await BlocksAuditsRepository.$migrateAuditsV0toV1(); await BlocksRepository.$migrateBlocks(); + + void blocks.$generateFlagValuesDatabase(); // do not wait for classify blocks to finish - blocks.$classifyBlocks(); + void blocks.$classifyBlocks(); + runSuccessful = true; } catch (e) { - this.indexerRunning = false; + nextRunDelay = retryDelay; logger.err(`Indexer failed, trying again in 10 seconds. Reason: ` + (e instanceof Error ? e.message : e)); - setTimeout(() => this.reindex(), 10000); + } finally { this.indexerRunning = false; - return; + const nextRunAt = new Date(Date.now() + nextRunDelay).toUTCString(); + if (runSuccessful) { + logger.debug(`Indexing completed. Next run planned at ${nextRunAt}`); + } else { + logger.debug(`Indexing did not complete, next run planned at ${nextRunAt}`); + } + this.scheduleNextRun(nextRunDelay); } - - this.indexerRunning = false; - - const runEvery = 1000 * 3600; // 1 hour - logger.debug(`Indexing completed. Next run planned at ${new Date(new Date().getTime() + runEvery).toUTCString()}`); - setTimeout(() => this.reindex(), runEvery); } } diff --git a/backend/src/logger.ts b/backend/src/logger.ts index 27aa942e6..879dbab5b 100644 --- a/backend/src/logger.ts +++ b/backend/src/logger.ts @@ -36,7 +36,7 @@ class Logger { mining: 'Mining', ln: 'Lightning', goggles: 'Goggles', - }; + }; // @ts-ignore public emerg: ((msg: string, tag?: string) => void); @@ -86,7 +86,7 @@ class Logger { private getNetwork(): string { if (config.LIGHTNING.ENABLED) { - return config.MEMPOOL.NETWORK === 'mainnet' ? 'lightning' : `${config.MEMPOOL.NETWORK}-lightning`; + return config.MEMPOOL.NETWORK === 'mainnet' ? 'lightning' : `${config.MEMPOOL.NETWORK}-lightning`; } if (config.MEMPOOL.NETWORK && config.MEMPOOL.NETWORK !== 'mainnet') { return config.MEMPOOL.NETWORK; diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index 0dfce6b1f..3f1403518 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -17,6 +17,7 @@ export interface PoolInfo { name: string; link: string; blockCount: number; + emptyBlocks: number; slug: string; avgMatchRate: number | null; avgFeeDelta: number | null; @@ -25,7 +26,11 @@ export interface PoolInfo { export interface PoolStats extends PoolInfo { rank: number; - emptyBlocks: number; +} + +export enum TemplateAlgorithm { + legacy = 0, + clusterMempool = 1, } export interface BlockAudit { @@ -45,6 +50,7 @@ export interface BlockAudit { expectedFees?: number, expectedWeight?: number, template?: any[]; + templateAlgorithm?: TemplateAlgorithm, } export interface TransactionAudit { @@ -132,6 +138,8 @@ export interface TransactionExtended extends IEsploraApi.Transaction { replacement?: boolean; uid?: number; flags?: number; + clusterId?: number; + chunkIndex?: number; } export interface MempoolTransactionExtended extends TransactionExtended { @@ -227,6 +235,7 @@ export interface CpfpInfo { adjustedVsize?: number, acceleration?: boolean, fee?: number; + cluster?: CpfpClusterData & { chunkIndex: number }; } export interface TransactionStripped { @@ -388,6 +397,25 @@ export interface CpfpCluster { height: number, txs: Ancestor[], effectiveFeePerVsize: number, + templateAlgorithm?: TemplateAlgorithm, + clusterData?: CpfpClusterData, +} + +export interface CpfpClusterTx { + txid: string; + fee: number; + weight: number; + parents: number[]; +} + +export interface CpfpClusterChunk { + txs: number[]; + feerate: number; +} + +export interface CpfpClusterData { + txs: CpfpClusterTx[]; + chunks: CpfpClusterChunk[]; } export interface CpfpSummary { @@ -504,9 +532,36 @@ export interface IBackendInfo { gitCommit: string; version: string; lightning: boolean; + coreVersion: string; + osVersion: string; backend: 'esplora' | 'electrum' | 'none'; } +export interface INetworkInfo { + version: number; + subversion: string; + protocolversion: number; + localservices: string; + localrelay: boolean; + timeoffset: number; + networkactive: boolean; + networks: { + name: string; + limited: boolean; + reachable: boolean; + proxy: string; + proxy_randomize_credentials: boolean; + }[]; + relayfee: number; + incrementalfee: number; + localaddresses: { + address: string; + port: number; + score: number; + }[]; + warnings: string; +} + export interface IDifficultyAdjustment { progressPercent: number; difficultyChange: number; diff --git a/backend/src/replication/AuditReplication.ts b/backend/src/replication/AuditReplication.ts index 6f616dbbe..562de5e2f 100644 --- a/backend/src/replication/AuditReplication.ts +++ b/backend/src/replication/AuditReplication.ts @@ -17,6 +17,7 @@ class AuditReplication { inProgress: boolean = false; skip: Set = new Set(); + /** @asyncUnsafe */ public async $sync(): Promise { if (!config.REPLICATION.ENABLED || !config.REPLICATION.AUDIT) { // replication not enabled @@ -54,6 +55,7 @@ class AuditReplication { this.inProgress = false; } + /** @asyncUnsafe */ private async $syncAudit(hash: string): Promise { if (this.skip.has(hash)) { // we already know none of our trusted servers have this audit @@ -77,6 +79,8 @@ class AuditReplication { return success; } + + /** @asyncSafe */ private async $getMissingAuditBlocks(): Promise { try { const startHeight = config.REPLICATION.AUDIT_START_HEIGHT || 0; @@ -110,6 +114,7 @@ class AuditReplication { }); await blocksAuditsRepository.$saveAudit({ version: auditSummary.version || 0, + templateAlgorithm: auditSummary.templateAlgorithm ?? 0, hash: blockHash, height: auditSummary.height, time: auditSummary.timestamp || auditSummary.time, diff --git a/backend/src/replication/StatisticsReplication.ts b/backend/src/replication/StatisticsReplication.ts index 49259b458..9569386f2 100644 --- a/backend/src/replication/StatisticsReplication.ts +++ b/backend/src/replication/StatisticsReplication.ts @@ -31,6 +31,7 @@ const steps = { class StatisticsReplication { inProgress: boolean = false; + /** @asyncUnsafe */ public async $sync(): Promise { if (!config.REPLICATION.ENABLED || !config.REPLICATION.STATISTICS || !config.STATISTICS.ENABLED) { // replication not enabled, or statistics not enabled @@ -51,12 +52,12 @@ class StatisticsReplication { logger.info(`Statistics table is complete, no replication needed`, 'Replication'); return; } - + for (const interval of missingIntervals) { logger.debug(`Missing ${missingStatistics[interval].size} statistics rows in '${interval}' timespan`, 'Replication'); } logger.debug(`Fetching ${missingIntervals.join(', ')} statistics endpoints from trusted servers to fill ${totalMissing} rows missing in statistics`, 'Replication'); - + let totalSynced = 0; let totalMissed = 0; @@ -74,16 +75,17 @@ class StatisticsReplication { this.inProgress = false; } + /** @asyncUnsafe */ private async $syncStatistics(interval: string, missingTimes: Set): Promise { - + let success = false; let synced = 0; - let missed = new Set(missingTimes); + const missed = new Set(missingTimes); const syncResult = await $sync(`/api/v1/statistics/${interval}`); if (syncResult && syncResult.data?.length) { success = true; - logger.info(`Fetched /api/v1/statistics/${interval} from ${syncResult.server}`); - + logger.info(`Fetched /api/v1/statistics/${interval} from ${syncResult.server}`); + for (const stat of syncResult.data) { const time = this.roundToNearestStep(stat.added, steps[interval]); if (missingTimes.has(time)) { @@ -105,6 +107,8 @@ class StatisticsReplication { return { success, synced, missed: missed.size }; } + + /** @asyncUnsafe */ private async $getMissingStatistics(): Promise { try { const now = Math.floor(Date.now() / 1000); @@ -129,7 +133,7 @@ class StatisticsReplication { startTime < now - day * 30 ? [now - day * 90, now - day * 30, '3m' ] : null, // from 3 months ago to 1 month ago = 2 hours granularity startTime < now - day * 90 ? [now - day * 180, now - day * 90, '6m' ] : null, // from 6 months ago to 3 months ago = 3 hours granularity startTime < now - day * 180 ? [now - day * 365 * 2, now - day * 180, '2y' ] : null, // from 2 years ago to 6 months ago = 8 hours granularity - startTime < now - day * 365 * 2 ? [startTime, now - day * 365 * 2, 'all'] : null, // from start of statistics to 2 years ago = 12 hours granularity + startTime < now - day * 365 * 2 ? [startTime, now - day * 365 * 2, 'all'] : null, // from start of statistics to 2 years ago = 12 hours granularity ]; for (const interval of intervals) { @@ -138,7 +142,7 @@ class StatisticsReplication { } missingStatistics[interval[2] as string] = await this.$getMissingStatisticsInterval(interval, startTime); } - + return missingStatistics; } catch (e: any) { logger.err(`Cannot fetch missing statistics times from db. Reason: ` + (e instanceof Error ? e.message : e)); @@ -146,6 +150,7 @@ class StatisticsReplication { } } + /** @asyncUnsafe */ private async $getMissingStatisticsInterval(interval: any, startTime: number): Promise> { try { const start = interval[0]; @@ -169,17 +174,17 @@ class StatisticsReplication { if (timeSteps.length === 0) { return new Set(); } - + const roundedTimesAlreadyHere: number[] = Array.from(new Set(rows.map(row => this.roundToNearestStep(row.added, step)))); const missingTimes = timeSteps.filter(time => !roundedTimesAlreadyHere.includes(time)).filter((time, i, arr) => { // Remove outsiders if (i === 0) { - return arr[i + 1] === time + step + return arr[i + 1] === time + step; } else if (i === arr.length - 1) { return arr[i - 1] === time - step; } - return (arr[i + 1] === time + step) && (arr[i - 1] === time - step) + return (arr[i + 1] === time + step) && (arr[i - 1] === time - step); }); // Don't bother fetching if very few rows are missing diff --git a/backend/src/replication/replicator.ts b/backend/src/replication/replicator.ts index ac204efcc..853dbdb39 100644 --- a/backend/src/replication/replicator.ts +++ b/backend/src/replication/replicator.ts @@ -4,6 +4,7 @@ import axios, { AxiosResponse } from 'axios'; import { SocksProxyAgent } from 'socks-proxy-agent'; import * as https from 'https'; +/** @asyncSafe */ export async function $sync(path): Promise<{ data?: any, exists: boolean, server?: string }> { // start with a random server so load is uniformly spread let allMissing = true; @@ -14,7 +15,7 @@ export async function $sync(path): Promise<{ data?: any, exists: boolean, server if (server === backendInfo.getBackendInfo().hostname) { continue; } - + try { const result = await query(`https://${server}${path}`); if (result) { @@ -33,6 +34,7 @@ export async function $sync(path): Promise<{ data?: any, exists: boolean, server return { exists: !allMissing }; } +/** @asyncUnsafe */ export async function query(path): Promise { type axiosOptions = { headers: { diff --git a/backend/src/repositories/AccelerationRepository.ts b/backend/src/repositories/AccelerationRepository.ts index aa39c8929..a45a80621 100644 --- a/backend/src/repositories/AccelerationRepository.ts +++ b/backend/src/repositories/AccelerationRepository.ts @@ -31,6 +31,7 @@ export interface PublicAcceleration { class AccelerationRepository { private bidBoostV2Activated = 831580; + /** @asyncSafe */ public async $saveAcceleration(acceleration: AccelerationInfo, block: IEsploraApi.Block, pool_id: number, accelerationData: Acceleration[]): Promise { const accelerationMap: { [txid: string]: Acceleration } = {}; for (const acc of accelerationData) { @@ -60,28 +61,34 @@ class AccelerationRepository { } } + /** @asyncSafe */ public async $getAccelerationInfoForTxid(txid: string): Promise { - const [rows] = await DB.query(` - SELECT *, UNIX_TIMESTAMP(requested) as requested_timestamp, UNIX_TIMESTAMP(added) as block_timestamp FROM accelerations - JOIN pools on pools.unique_id = accelerations.pool - WHERE txid = ? - `, [txid]) as RowDataPacket[][]; - if (rows?.length) { - const row = rows[0]; - return { - txid: row.txid, - height: row.height, - added: row.requested_timestamp || row.block_timestamp, - pool: { - id: row.id, - slug: row.slug, - name: row.name, - }, - effective_vsize: row.effective_vsize, - effective_fee: row.effective_fee, - boost_rate: row.boost_rate, - boost_cost: row.boost_cost, - }; + try { + const [rows] = await DB.query(` + SELECT *, UNIX_TIMESTAMP(requested) as requested_timestamp, UNIX_TIMESTAMP(added) as block_timestamp FROM accelerations + JOIN pools on pools.unique_id = accelerations.pool + WHERE txid = ? + `, [txid]) as RowDataPacket[][]; + if (rows?.length) { + const row = rows[0]; + return { + txid: row.txid, + height: row.height, + added: row.requested_timestamp || row.block_timestamp, + pool: { + id: row.id, + slug: row.slug, + name: row.name, + }, + effective_vsize: row.effective_vsize, + effective_fee: row.effective_fee, + boost_rate: row.boost_rate, + boost_cost: row.boost_cost, + }; + } + } catch (e: any) { + logger.err(`Cannot get acceleration info for txid ${txid}. Reason: ` + (e instanceof Error ? e.message : e)); + return null; } return null; } @@ -100,7 +107,7 @@ class AccelerationRepository { SELECT *, UNIX_TIMESTAMP(requested) as requested_timestamp, UNIX_TIMESTAMP(added) as block_timestamp FROM accelerations JOIN pools on pools.unique_id = accelerations.pool `; - let params: any[] = []; + const params: any[] = []; let hasFilter = false; if (interval && height === null) { @@ -163,7 +170,7 @@ class AccelerationRepository { SELECT SUM(boost_cost) as total_cost, COUNT(txid) as count FROM accelerations JOIN pools on pools.unique_id = accelerations.pool `; - let params: any[] = []; + const params: any[] = []; let hasFilter = false; if (interval) { @@ -191,6 +198,7 @@ class AccelerationRepository { } } + /** @asyncSafe */ public async $getLastSyncedHeight(): Promise { try { const [rows] = await DB.query(` @@ -206,6 +214,7 @@ class AccelerationRepository { return 0; } + /** @asyncSafe */ private async $setLastSyncedHeight(height: number): Promise { try { await DB.query(` @@ -219,6 +228,7 @@ class AccelerationRepository { } // modifies block transactions + /** @asyncSafe */ public async $indexAccelerationsForBlock(block: BlockExtended, accelerations: Acceleration[], transactions: MempoolTransactionExtended[]): Promise { const blockTxs: { [txid: string]: MempoolTransactionExtended } = {}; for (const tx of transactions) { @@ -237,7 +247,7 @@ class AccelerationRepository { const tx = blockTxs[acc.txid]; const accelerationInfo = accelerationCosts.getAccelerationInfo(tx, boostRate, transactions); accelerationInfo.cost = Math.max(0, Math.min(acc.feeDelta, accelerationInfo.cost)); - this.$saveAcceleration(accelerationInfo, block, block.extras.pool.id, successfulAccelerations); + void this.$saveAcceleration(accelerationInfo, block, block.extras.pool.id, successfulAccelerations); } } let anyConfirmed = false; @@ -346,7 +356,7 @@ class AccelerationRepository { const accelerationSummaries = accelerations.map(acc => ({ ...acc, pools: acc.pools, - })) + })); for (const acc of accelerations) { if (blockTxs[acc.txid] && acc.pools.includes(block.extras.pool.id)) { const tx = blockTxs[acc.txid]; diff --git a/backend/src/repositories/BlocksAuditsRepository.ts b/backend/src/repositories/BlocksAuditsRepository.ts index 3b3f79ce0..9d1be0eb6 100644 --- a/backend/src/repositories/BlocksAuditsRepository.ts +++ b/backend/src/repositories/BlocksAuditsRepository.ts @@ -1,7 +1,7 @@ import DB from '../database'; import logger from '../logger'; import bitcoinApi from '../api/bitcoin/bitcoin-api-factory'; -import { BlockAudit, AuditScore, TransactionAudit, TransactionStripped } from '../mempool.interfaces'; +import { BlockAudit, AuditScore, TransactionAudit, TransactionStripped, TemplateAlgorithm } from '../mempool.interfaces'; interface MigrationAudit { version: number, @@ -15,10 +15,11 @@ interface MigrationAudit { } class BlocksAuditRepositories { + /** @asyncSafe */ public async $saveAudit(audit: BlockAudit): Promise { try { - await DB.query(`INSERT INTO blocks_audits(version, time, height, hash, unseen_txs, missing_txs, added_txs, prioritized_txs, fresh_txs, sigop_txs, fullrbf_txs, accelerated_txs, match_rate, expected_fees, expected_weight) - VALUE (?, FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [audit.version, audit.time, audit.height, audit.hash, JSON.stringify(audit.unseenTxs), JSON.stringify(audit.missingTxs), + await DB.query(`INSERT INTO blocks_audits(version, template_algo, time, height, hash, unseen_txs, missing_txs, added_txs, prioritized_txs, fresh_txs, sigop_txs, fullrbf_txs, accelerated_txs, match_rate, expected_fees, expected_weight) + VALUE (?, ?, FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [audit.version, audit.templateAlgorithm ?? 0, audit.time, audit.height, audit.hash, JSON.stringify(audit.unseenTxs), JSON.stringify(audit.missingTxs), JSON.stringify(audit.addedTxs), JSON.stringify(audit.prioritizedTxs), JSON.stringify(audit.freshTxs), JSON.stringify(audit.sigopTxs), JSON.stringify(audit.fullrbfTxs), JSON.stringify(audit.acceleratedTxs), audit.matchRate, audit.expectedFees, audit.expectedWeight]); } catch (e: any) { if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart @@ -29,6 +30,7 @@ class BlocksAuditRepositories { } } + /** @asyncSafe */ public async $setSummary(hash: string, expectedFees: number, expectedWeight: number) { try { await DB.query(` @@ -42,6 +44,7 @@ class BlocksAuditRepositories { } } + /** @asyncSafe */ public async $getBlocksHealthHistory(div: number, interval: string | null): Promise { try { let query = `SELECT UNIX_TIMESTAMP(time) as time, height, match_rate FROM blocks_audits`; @@ -60,6 +63,7 @@ class BlocksAuditRepositories { } } + /** @asyncSafe */ public async $getBlocksHealthCount(): Promise { try { const [rows] = await DB.query(`SELECT count(hash) as count FROM blocks_audits`); @@ -70,11 +74,13 @@ class BlocksAuditRepositories { } } + /** @asyncSafe */ public async $getBlockAudit(hash: string): Promise { try { const [rows]: any[] = await DB.query( `SELECT blocks_audits.version, + blocks_audits.template_algo as templateAlgorithm, blocks_audits.height, blocks_audits.hash as id, UNIX_TIMESTAMP(blocks_audits.time) as timestamp, @@ -94,7 +100,7 @@ class BlocksAuditRepositories { JOIN blocks_templates ON blocks_templates.id = blocks_audits.hash WHERE blocks_audits.hash = ? `, [hash]); - + if (rows.length) { rows[0].unseenTxs = JSON.parse(rows[0].unseenTxs); rows[0].missingTxs = JSON.parse(rows[0].missingTxs); @@ -115,6 +121,24 @@ class BlocksAuditRepositories { } } + /** @asyncSafe */ + public async $getBlockTemplateAlgo(hash: string): Promise { + try { + const [rows]: any[] = await DB.query( + `SELECT template_algo FROM blocks_audits WHERE hash = ?`, + [hash] + ); + if (rows.length) { + return rows[0].template_algo as TemplateAlgorithm; + } + return null; + } catch (e: any) { + logger.err(`Cannot fetch block template algo from db. Reason: ` + (e instanceof Error ? e.message : e)); + return null; + } + } + + /** @asyncSafe */ public async $getBlockTxAudit(hash: string, txid: string): Promise { try { const blockAudit = await this.$getBlockAudit(hash); @@ -151,6 +175,7 @@ class BlocksAuditRepositories { } } + /** @asyncSafe */ public async $getBlockAuditScore(hash: string): Promise { try { const [rows]: any[] = await DB.query( @@ -165,6 +190,7 @@ class BlocksAuditRepositories { } } + /** @asyncSafe */ public async $getBlockAuditScores(maxHeight: number, minHeight: number): Promise { try { const [rows]: any[] = await DB.query( @@ -179,6 +205,7 @@ class BlocksAuditRepositories { } } + /** @asyncSafe */ public async $getBlocksWithoutSummaries(): Promise { try { const [fromRows]: any[] = await DB.query(` @@ -207,6 +234,7 @@ class BlocksAuditRepositories { /** * [INDEXING] Migrate audits from v0 to v1 + * @asyncSafe */ public async $migrateAuditsV0toV1(): Promise { try { diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 2994dd8f4..20d0a6e46 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -14,7 +14,7 @@ import chainTips from '../api/chain-tips'; import blocks from '../api/blocks'; import BlocksAuditsRepository from './BlocksAuditsRepository'; import transactionUtils from '../api/transaction-utils'; -import { parseDATUMTemplateCreator } from '../utils/bitcoin-script'; +import { parseDATUMTemplateCreator, parseDMNDTemplateCreator } from '../utils/bitcoin-script'; import poolsUpdater from '../tasks/pools-updater'; interface DatabaseBlock { @@ -59,7 +59,7 @@ interface DatabaseBlock { utxoSetChange: number; utxoSetSize: number; totalInputAmt: number; - firstSeen: number; + firstSeen: string; // UNIX_TIMESTAMP() returns a string when applied to datetime(6) stale: boolean; } @@ -114,6 +114,7 @@ class BlocksRepository { /** * Save indexed block data in the database + * @asyncSafe */ public async $saveBlockInDatabase(block: BlockExtended) { const truncatedCoinbaseSignature = block?.extras?.coinbaseSignature?.substring(0, 500); @@ -131,7 +132,7 @@ class BlocksRepository { total_inputs, total_outputs, total_input_amt, total_output_amt, fee_percentiles, segwit_total_txs, segwit_total_size, segwit_total_weight, median_fee_amt, coinbase_signature_ascii, definition_hash, index_version, - stale + stale, first_seen ) VALUE ( ?, ?, FROM_UNIXTIME(?), ?, ?, ?, ?, ?, @@ -143,7 +144,7 @@ class BlocksRepository { ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, - ? + ?, FROM_UNIXTIME(?) )`; const poolDbId = await PoolsRepository.$getPoolByUniqueId(block.extras.pool.id); @@ -193,6 +194,7 @@ class BlocksRepository { poolsUpdater.currentSha, BlocksRepository.version, (block.stale ? 1 : 0), + block.extras.firstSeen === null ? 1 : block.extras.firstSeen // Sentinel value 1 indicates that we could not find first seen time ]; await DB.query(query, params); @@ -217,9 +219,10 @@ class BlocksRepository { /** * Save newly indexed data from core coinstatsindex - * - * @param utxoSetSize - * @param totalInputAmt + * + * @param utxoSetSize + * @param totalInputAmt + * @asyncSafe */ public async $updateCoinStatsIndexData(blockHash: string, utxoSetSize: number, totalInputAmt: number @@ -245,9 +248,10 @@ class BlocksRepository { /** * Update missing fee amounts fields * - * @param blockHash - * @param feeAmtPercentiles - * @param medianFeeAmt + * @param blockHash + * @param feeAmtPercentiles + * @param medianFeeAmt + * @asyncSafe */ public async $updateFeeAmounts(blockHash: string, feeAmtPercentiles, medianFeeAmt) : Promise { try { @@ -270,12 +274,13 @@ class BlocksRepository { /** * Get all block height that have not been indexed between [startHeight, endHeight] + * @asyncSafe */ public async $getMissingBlocksBetweenHeights(startHeight: number, endHeight: number): Promise { // Ensure startHeight is the lower value and endHeight is the higher value const minHeight = Math.min(startHeight, endHeight); const maxHeight = Math.max(startHeight, endHeight); - + if (minHeight === maxHeight) { return []; } @@ -300,40 +305,9 @@ class BlocksRepository { } } - /** - * Get empty blocks for one or all pools - */ - public async $countEmptyBlocks(poolId: number | null, interval: string | null = null): Promise { - interval = Common.getSqlInterval(interval); - - const params: any[] = []; - let query = `SELECT count(height) as count, pools.id as poolId - FROM blocks - JOIN pools on pools.id = blocks.pool_id - WHERE tx_count = 1 AND stale = 0`; - - if (poolId) { - query += ` AND pool_id = ?`; - params.push(poolId); - } - - if (interval) { - query += ` AND blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; - } - - query += ` GROUP by pools.id`; - - try { - const [rows] = await DB.query(query, params); - return rows; - } catch (e) { - logger.err('Cannot count empty blocks. Reason: ' + (e instanceof Error ? e.message : e)); - throw e; - } - } - /** * Return most recent block height + * @asyncSafe */ public async $mostRecentBlockHeight(): Promise { try { @@ -347,22 +321,32 @@ class BlocksRepository { /** * Get blocks count for a period + * @asyncSafe */ public async $blockCount(poolId: number | null, interval: string | null = null): Promise { interval = Common.getSqlInterval(interval); const params: any[] = []; - let query = `SELECT count(height) as blockCount - FROM blocks - WHERE stale = 0`; - - if (poolId) { - query += ` AND pool_id = ?`; + let query; + if (!poolId && !interval) { + // optimized query to get full indexed block count + query = `SELECT CAST(COALESCE(MAX(height) - MIN(height) + 1, 0) AS SIGNED) as blockCount FROM blocks`; + } else if (!poolId) { + // optimized query for indexed count within an interval + query = `SELECT GREATEST(0, COALESCE( + CAST((SELECT height FROM blocks WHERE stale = 0 AND blockTimestamp <= NOW() ORDER BY blockTimestamp DESC LIMIT 1) AS SIGNED) - + CAST((SELECT height FROM blocks WHERE stale = 0 AND blockTimestamp >= DATE_SUB(NOW(), INTERVAL ${interval}) ORDER BY blockTimestamp ASC LIMIT 1) AS SIGNED) + 1 + , 0)) as blockCount`; + } else { + // for specific pools, we do still have to actually count the blocks + query = `SELECT count(*) as blockCount + FROM blocks + WHERE stale = 0 AND pool_id = ?`; params.push(poolId); - } - if (interval) { - query += ` AND blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; + if (interval) { + query += ` AND blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; + } } try { @@ -380,6 +364,7 @@ class BlocksRepository { * @param from - The oldest timestamp * @param to - The newest timestamp * @returns + * @asyncSafe */ public async $blockCountBetweenTimestamp(poolId: number | null, from: number, to: number): Promise { const params: any[] = []; @@ -407,10 +392,11 @@ class BlocksRepository { /** * Get blocks count for a period + * @asyncSafe */ public async $blockCountBetweenHeight(startHeight: number, endHeight: number): Promise { const params: any[] = []; - let query = `SELECT count(height) as blockCount + const query = `SELECT count(height) as blockCount FROM blocks WHERE height <= ${startHeight} AND height >= ${endHeight} AND stale = 0`; @@ -425,6 +411,7 @@ class BlocksRepository { /** * Get average block health for all blocks for a single pool + * @asyncSafe */ public async $getAvgBlockHealthPerPoolId(poolId: number): Promise { const params: any[] = []; @@ -450,6 +437,7 @@ class BlocksRepository { /** * Get average block health for all blocks for a single pool + * @asyncSafe */ public async $getTotalRewardForPoolId(poolId: number): Promise { const params: any[] = []; @@ -474,6 +462,7 @@ class BlocksRepository { /** * Get the oldest indexed block + * @asyncSafe */ public async $oldestBlockTimestamp(): Promise { const query = `SELECT UNIX_TIMESTAMP(blockTimestamp) as blockTimestamp @@ -498,6 +487,7 @@ class BlocksRepository { /** * Get blocks mined by a specific mining pool + * @asyncSafe */ public async $getBlocksByPool(slug: string, startHeight?: number): Promise { const pool = await PoolsRepository.$getPool(slug); @@ -538,6 +528,7 @@ class BlocksRepository { /** * Get one block by height + * @asyncSafe */ public async $getBlockByHeight(height: number): Promise { try { @@ -560,8 +551,34 @@ class BlocksRepository { } } + /** + * Get the canonical block hash at a given height + * @asyncSafe + */ + public async $getCanonicalBlockHashByHeight(height: number): Promise { + try { + const [rows]: any[] = await DB.query(` + SELECT hash + FROM blocks + WHERE height = ? AND stale = 0 + LIMIT 1`, + [height] + ); + + if (rows.length <= 0) { + return null; + } + + return rows[0].hash; + } catch (e) { + logger.err(`Cannot get canonical block hash at height ${height}. Reason: ` + (e instanceof Error ? e.message : e)); + throw e; + } + } + /** * Get one block by hash + * @asyncSafe */ public async $getBlockByHash(hash: string): Promise { try { @@ -586,6 +603,7 @@ class BlocksRepository { /** * Return blocks difficulty + * @asyncSafe */ public async $getBlocksDifficulty(): Promise { try { @@ -601,6 +619,7 @@ class BlocksRepository { * Get the first block at or directly after a given timestamp * @param timestamp number unix time in seconds * @returns The height and timestamp of a block (timestamp might vary from given timestamp) + * @asyncSafe */ public async $getBlockHeightFromTimestamp( timestamp: number, @@ -629,6 +648,7 @@ class BlocksRepository { /** * Get general block stats + * @asyncSafe */ public async $getBlockStats(blockCount: number): Promise { try { @@ -652,12 +672,14 @@ class BlocksRepository { /** * Check if the canonical chain of blocks is valid and fix it if needed + * @asyncSafe */ public async $validateChain(): Promise { try { const start = new Date().getTime(); const tip = await bitcoinApi.$getBlockHashTip(); let firstBadBlockHeight: number | null = null; + let firstBadBlockTimestamp: number | null = null; const [blocks]: any[] = await DB.query(` SELECT height, @@ -668,6 +690,9 @@ class BlocksRepository { FROM blocks ORDER BY height DESC `); + if (!blocks || blocks.length === 0) { + throw new Error('Cannot validate chain: no indexed blocks in database'); + } const blocksByHash = {}; const blocksByHeight = {}; let minHeight = Infinity; @@ -684,7 +709,11 @@ class BlocksRepository { // ensure that indexed blocks are correctly classified as stale or canonical // iterate back to genesis, resetting canonical status where necessary let hash = tip; - const tipHeight = blocksByHash[hash].height || (await bitcoinApi.$getBlock(hash))?.height; + const indexedTip = blocksByHash[hash]; + const tipHeight = indexedTip?.height ?? (await bitcoinApi.$getBlock(hash))?.height; + if (typeof tipHeight !== 'number') { + throw new Error(`Cannot validate chain: could not resolve tip block height for ${hash} from index or node`); + } // stop at the last canonical block we're supposed to have indexed already let lastIndexedBlockHeight = minHeight; @@ -706,6 +735,7 @@ class BlocksRepository { // block is marked stale, but shouldn't be await this.$setCanonicalBlockAtHeight(block.hash, height); firstBadBlockHeight = height; + firstBadBlockTimestamp = block.timestamp; } hash = block?.previous_block_hash; if (!hash) { @@ -722,7 +752,9 @@ class BlocksRepository { if (firstBadBlockHeight != null) { logger.warn(`Chain divergence detected at block ${firstBadBlockHeight}`); - await HashratesRepository.$deleteHashratesFromTimestamp(blocksByHash[firstBadBlockHeight].timestamp - 604800); + if (firstBadBlockTimestamp != null) { + await HashratesRepository.$deleteHashratesFromTimestamp(firstBadBlockTimestamp - 604800); + } await DifficultyAdjustmentsRepository.$deleteAdjustementsFromHeight(firstBadBlockHeight); return false; } @@ -737,6 +769,7 @@ class BlocksRepository { /** * Get the historical averaged block fees + * @asyncSafe */ public async $getHistoricalBlockFees(div: number, interval: string | null, timespan?: {from: number, to: number}): Promise { try { @@ -769,6 +802,7 @@ class BlocksRepository { /** * Get the historical averaged block rewards + * @asyncSafe */ public async $getHistoricalBlockRewards(div: number, interval: string | null): Promise { try { @@ -799,6 +833,7 @@ class BlocksRepository { /** * Get the historical averaged block fee rate percentiles + * @asyncSafe */ public async $getHistoricalBlockFeeRates(div: number, interval: string | null): Promise { try { @@ -831,6 +866,7 @@ class BlocksRepository { /** * Get the historical averaged block sizes + * @asyncSafe */ public async $getHistoricalBlockSizes(div: number, interval: string | null): Promise { try { @@ -857,6 +893,7 @@ class BlocksRepository { /** * Get the historical averaged block weights + * @asyncSafe */ public async $getHistoricalBlockWeights(div: number, interval: string | null): Promise { try { @@ -884,6 +921,7 @@ class BlocksRepository { /** * Get a list of blocks that have been indexed * (includes stale blocks) + * @asyncSafe */ public async $getIndexedBlocks(): Promise<{ height: number, hash: string, stale: boolean }[]> { try { @@ -897,6 +935,7 @@ class BlocksRepository { /** * Get a list of blocks that have not had CPFP data indexed + * @asyncSafe */ public async $getCPFPUnindexedBlocks(): Promise { try { @@ -930,6 +969,7 @@ class BlocksRepository { /** * Return the oldest block from a consecutive chain of block from the most recent one + * @asyncSafe */ public async $getOldestConsecutiveBlock(): Promise { try { @@ -948,6 +988,7 @@ class BlocksRepository { /** * Get all blocks which have not be linked to a price yet + * @asyncSafe */ public async $getBlocksWithoutPrice(): Promise { try { @@ -969,6 +1010,7 @@ class BlocksRepository { /** * Save block price by batch + * @asyncSafe */ public async $saveBlockPrices(blockPrices: BlockPrice[]): Promise { try { @@ -990,6 +1032,7 @@ class BlocksRepository { /** * Get all indexed blocsk with missing coinstatsindex data + * @asyncSafe */ public async $getBlocksMissingCoinStatsIndex(maxHeight: number, minHeight: number): Promise { try { @@ -1009,6 +1052,7 @@ class BlocksRepository { /** * Get all indexed blocks with missing coinbase addresses * (includes stale blocks) + * @asyncSafe */ public async $getBlocksWithoutCoinbaseAddresses(): Promise { try { @@ -1028,9 +1072,10 @@ class BlocksRepository { /** * Save indexed median fee to avoid recomputing it later - * - * @param id - * @param feePercentiles + * + * @param id + * @param feePercentiles + * @asyncSafe */ public async $saveFeePercentilesForBlockId(id: string, feePercentiles: number[]): Promise { try { @@ -1047,9 +1092,10 @@ class BlocksRepository { /** * Save indexed effective fee statistics - * - * @param id - * @param feeStats + * + * @param id + * @param feeStats + * @asyncSafe */ public async $saveEffectiveFeeStats(id: string, feeStats: EffectiveFeeStats): Promise { try { @@ -1066,9 +1112,10 @@ class BlocksRepository { /** * Save coinbase addresses - * + * * @param id * @param addresses + * @asyncSafe */ public async $saveCoinbaseAddresses(id: string, addresses: string[]): Promise { try { @@ -1085,9 +1132,10 @@ class BlocksRepository { /** * Save pool - * + * * @param id * @param poolId + * @asyncSafe */ public async $savePool(id: string, poolId: number): Promise { try { @@ -1103,26 +1151,62 @@ class BlocksRepository { } /** - * Save block first seen time - * - * @param id + * Save block first seen times + * + * @param results + * @asyncSafe */ - public async $saveFirstSeenTime(id: string, firstSeen: number): Promise { + public async $saveFirstSeenTimes(results: { hash: string; firstSeen: number | null }[]): Promise { + if (!results.length) { + return; + } + const CHUNK_SIZE = 1000; + for (let i = 0; i < results.length; i += CHUNK_SIZE) { + const chunk = results.slice(i, i + CHUNK_SIZE); + const params: Array = []; + const selects = chunk.map(() => 'SELECT ? AS hash, FROM_UNIXTIME(?) AS first_seen').join(' UNION ALL '); + for (const { hash, firstSeen } of chunk) { + params.push(hash, firstSeen === null ? 1 : firstSeen); // Sentinel value 1 indicates that we could not find first seen time + } + const query = ` + UPDATE blocks AS b + JOIN ( + ${selects} + ) AS updates ON updates.hash = b.hash + SET b.first_seen = updates.first_seen + `; + try { + await DB.query(query, params); + } catch (e) { + logger.err(`Cannot batch update block first seen times. Reason: ` + (e instanceof Error ? e.message : e)); + throw e; + } + } + } + + /** + * Get all blocks which do not have a first seen time yet + * + * @param includeAlreadyTried Include blocks we have already tried to fetch first seen time for, identified by sentinel value 1 + */ + public async $getBlocksWithoutFirstSeen(includeAlreadyTried = false): Promise<{ hash: string; timestamp: number }[]> { try { - await DB.query(` - UPDATE blocks SET first_seen = FROM_UNIXTIME(?) - WHERE hash = ?`, - [firstSeen, id] - ); - } catch (e) { - logger.err(`Cannot update block first seen time. Reason: ` + (e instanceof Error ? e.message : e)); + const [rows]: any[] = await DB.query(` + SELECT hash, UNIX_TIMESTAMP(blockTimestamp) as timestamp + FROM blocks + WHERE first_seen IS NULL + ${includeAlreadyTried ? ' OR first_seen = FROM_UNIXTIME(1)' : ''} + `); + return rows; + } catch (e: any) { + logger.err(`Cannot fetch block first seen from db. Reason: ` + (e instanceof Error ? e.message : e)); throw e; } } /** * Change which block at a height belongs to the canonical chain - * + * * @param hash * @param height */ @@ -1151,8 +1235,9 @@ class BlocksRepository { /** * Convert a mysql row block into a BlockExtended. Note that you * must provide the correct field into dbBlk object param - * - * @param dbBlk + * + * @param dbBlk + * @asyncUnsafe */ private async formatDbBlockIntoExtendedBlock(dbBlk: DatabaseBlock): Promise { const blk: Partial = {}; @@ -1173,6 +1258,10 @@ class BlocksRepository { blk.previousblockhash = dbBlk.previousblockhash; blk.mediantime = dbBlk.mediantime; blk.indexVersion = dbBlk.index_version; + blk.stale = dbBlk.stale; + if (dbBlk.stale) { + blk.canonical = await this.$getCanonicalBlockHashByHeight(dbBlk.height) || undefined; + } // BlockExtension extras.totalFees = dbBlk.totalFees; extras.medianFee = dbBlk.medianFee; @@ -1205,7 +1294,14 @@ class BlocksRepository { extras.utxoSetSize = dbBlk.utxoSetSize; extras.totalInputAmt = dbBlk.totalInputAmt; extras.virtualSize = dbBlk.weight / 4.0; - extras.firstSeen = dbBlk.firstSeen; + + extras.firstSeen = null; + if (config.CORE_RPC.DEBUG_LOG_PATH) { + const dbFirstSeen = parseFloat(dbBlk.firstSeen); + if (dbFirstSeen > 1) { // Sentinel value 1 indicates that we could not find first seen time + extras.firstSeen = dbFirstSeen; + } + } // Re-org can happen after indexing so we need to always get the // latest state from core @@ -1254,6 +1350,8 @@ class BlocksRepository { if (extras.pool.name === 'OCEAN') { extras.pool.minerNames = parseDATUMTemplateCreator(extras.coinbaseRaw); + } else if (extras.pool.name === 'DMND') { + extras.pool.minerNames = parseDMNDTemplateCreator(extras.coinbaseRaw); } blk.extras = extras; @@ -1272,6 +1370,7 @@ class BlocksRepository { } // migration to fix median fee bug + /** @asyncSafe */ private async $migrateBlocksToV1(): Promise { let blocksMigrated = 0; try { diff --git a/backend/src/repositories/BlocksSummariesRepository.ts b/backend/src/repositories/BlocksSummariesRepository.ts index d0e3db848..2239fa258 100644 --- a/backend/src/repositories/BlocksSummariesRepository.ts +++ b/backend/src/repositories/BlocksSummariesRepository.ts @@ -5,6 +5,7 @@ import logger from '../logger'; import { BlockSummary, TransactionClassified } from '../mempool.interfaces'; class BlocksSummariesRepository { + /** @asyncSafe */ public async $getByBlockId(id: string): Promise { try { const [summary]: any[] = await DB.query(`SELECT * from blocks_summaries WHERE id = ?`, [id]); @@ -19,6 +20,7 @@ class BlocksSummariesRepository { return undefined; } + /** @asyncSafe */ public async $saveTransactions(blockHeight: number, blockId: string, transactions: TransactionClassified[], version: number): Promise { try { const transactionsStr = JSON.stringify(transactions); @@ -33,6 +35,7 @@ class BlocksSummariesRepository { } } + /** @asyncSafe */ public async $saveTemplate(params: { height: number, template: BlockSummary, version: number}): Promise { const blockId = params.template?.id; try { @@ -53,6 +56,7 @@ class BlocksSummariesRepository { } } + /** @asyncSafe */ public async $getTemplate(id: string): Promise { try { const [templates]: any[] = await DB.query(`SELECT * from blocks_templates WHERE id = ?`, [id]); @@ -69,6 +73,7 @@ class BlocksSummariesRepository { return undefined; } + /** @asyncSafe */ public async $getIndexedSummariesId(): Promise { try { const [rows] = await DB.query(`SELECT id from blocks_summaries`) as RowDataPacket[][]; @@ -80,6 +85,7 @@ class BlocksSummariesRepository { return []; } + /** @asyncSafe */ public async $getSummariesWithVersion(version: number): Promise<{ height: number, id: string }[]> { try { const [rows]: any[] = await DB.query(` @@ -97,6 +103,7 @@ class BlocksSummariesRepository { return []; } + /** @asyncSafe */ public async $getTemplatesWithVersion(version: number): Promise<{ height: number, id: string }[]> { try { const [rows]: any[] = await DB.query(` @@ -115,6 +122,7 @@ class BlocksSummariesRepository { return []; } + /** @asyncSafe */ public async $getSummariesBelowVersion(version: number): Promise<{ height: number, id: string, version: number }[]> { try { const [rows]: any[] = await DB.query(` @@ -133,6 +141,7 @@ class BlocksSummariesRepository { return []; } + /** @asyncSafe */ public async $getTemplatesBelowVersion(version: number): Promise<{ height: number, id: string, version: number }[]> { try { const [rows]: any[] = await DB.query(` @@ -154,8 +163,9 @@ class BlocksSummariesRepository { /** * Get the fee percentiles if the block has already been indexed, [] otherwise - * - * @param id + * + * @param id + * @asyncSafe */ public async $getFeePercentilesByBlockId(id: string): Promise { try { @@ -206,6 +216,34 @@ class BlocksSummariesRepository { } return false; } + + /** @asyncSafe */ + public async $getTipIndexed(): Promise { + if (!Common.blocksSummariesIndexingEnabled()) { + return null; + } + try { + const [row]: any[] = await DB.query('SELECT MAX(height) as tip FROM blocks_summaries WHERE version >= 1'); + + if (row !== null && row.length > 0) { + return row[0].tip; + } + } catch (e) { + logger.err(`Cannot get latest block summary. Reason: ` + (e instanceof Error ? e.message : e)); + } + return null; + } + + public async $getSummariesBetweenHeights(startHeight: number, latestHeight: number): Promise<{height: number, transactions: string, timestamp: number}[]> { + try { + const [rows]: any[] = await DB.query(`SELECT bs.height, bs.transactions, UNIX_TIMESTAMP(b.blockTimestamp) as timestamp FROM blocks_summaries bs JOIN blocks b ON bs.id = b.hash WHERE bs.height <= ? AND bs.height > ? AND b.stale = 0 AND bs.version >= 1 ORDER BY height DESC`, [startHeight, latestHeight]); + + return rows; + } catch (e) { + logger.err(`Cannot get blocks between ${startHeight} and ${latestHeight}. Reason: ` + (e instanceof Error ? e.message : e)); + throw e; + } + } } export default new BlocksSummariesRepository(); diff --git a/backend/src/repositories/CpfpRepository.ts b/backend/src/repositories/CpfpRepository.ts index 0242188df..f3f9dad5c 100644 --- a/backend/src/repositories/CpfpRepository.ts +++ b/backend/src/repositories/CpfpRepository.ts @@ -1,31 +1,47 @@ import { RowDataPacket } from 'mysql2'; import DB from '../database'; import logger from '../logger'; -import { Ancestor, CpfpCluster } from '../mempool.interfaces'; +import { Ancestor, CpfpCluster, CpfpClusterData, CpfpClusterTx, TemplateAlgorithm } from '../mempool.interfaces'; import transactionRepository from '../repositories/TransactionRepository'; class CpfpRepository { - public async $batchSaveClusters(clusters: { root: string, height: number, txs: Ancestor[], effectiveFeePerVsize: number }[]): Promise { + public async $batchSaveClusters(clusters: CpfpCluster[]): Promise { try { - const clusterValues: [string, number, Buffer, number][] = []; + const clusterValues: [string, number, Buffer, number, number][] = []; const txs: { txid: string, cluster: string }[] = []; for (const cluster of clusters) { if (cluster.txs?.length) { - const roundedEffectiveFee = Math.round(cluster.effectiveFeePerVsize * 100) / 100; - const equalFee = cluster.txs.length > 1 && cluster.txs.reduce((acc, tx) => { - return (acc && Math.round(((tx.fee || 0) / (tx.weight / 4)) * 100) / 100 === roundedEffectiveFee); - }, true); - if (!equalFee) { + const isCM = cluster.templateAlgorithm === TemplateAlgorithm.clusterMempool; + + if (isCM && cluster.clusterData) { clusterValues.push([ cluster.root, cluster.height, - Buffer.from(this.pack(cluster.txs)), - cluster.effectiveFeePerVsize + Buffer.from(this.packCM(cluster.clusterData)), + 0, + TemplateAlgorithm.clusterMempool, ]); - for (const tx of cluster.txs) { + for (const tx of cluster.clusterData.txs) { txs.push({ txid: tx.txid, cluster: cluster.root }); } + } else { + const roundedEffectiveFee = Math.round(cluster.effectiveFeePerVsize * 100) / 100; + const equalFee = cluster.txs.length > 1 && cluster.txs.reduce((acc, tx) => { + return (acc && Math.round(((tx.fee || 0) / (tx.weight / 4)) * 100) / 100 === roundedEffectiveFee); + }, true); + if (!equalFee) { + clusterValues.push([ + cluster.root, + cluster.height, + Buffer.from(this.pack(cluster.txs)), + cluster.effectiveFeePerVsize, + TemplateAlgorithm.legacy, + ]); + for (const tx of cluster.txs) { + txs.push({ txid: tx.txid, cluster: cluster.root }); + } + } } } } @@ -42,11 +58,11 @@ class CpfpRepository { while (chunkIndex < clusterValues.length) { const chunk = clusterValues.slice(chunkIndex, chunkIndex + maxChunk); let query = ` - INSERT IGNORE INTO compact_cpfp_clusters(root, height, txs, fee_rate) + INSERT IGNORE INTO compact_cpfp_clusters(root, height, txs, fee_rate, template_algo) VALUES `; query += chunk.map(chunk => { - return (' (UNHEX(?), ?, ?, ?)'); + return (' (UNHEX(?), ?, ?, ?, ?)'); }) + ';'; const values = chunk.flat(); queries.push({ @@ -73,6 +89,8 @@ class CpfpRepository { } } + + /** @asyncUnsafe */ public async $getCluster(clusterRoot: string): Promise { const [clusterRows]: any = await DB.query( ` @@ -84,13 +102,22 @@ class CpfpRepository { ); const cluster = clusterRows[0]; if (cluster?.txs) { - cluster.effectiveFeePerVsize = cluster.fee_rate; - cluster.txs = this.unpack(cluster.txs); + if (cluster.template_algo === TemplateAlgorithm.clusterMempool) { + cluster.templateAlgorithm = TemplateAlgorithm.clusterMempool; + cluster.clusterData = this.unpackCM(cluster.txs); + cluster.txs = cluster.clusterData.txs.map(tx => ({ txid: tx.txid, weight: tx.weight, fee: tx.fee })); + cluster.effectiveFeePerVsize = 0; + } else { + cluster.templateAlgorithm = TemplateAlgorithm.legacy; + cluster.effectiveFeePerVsize = cluster.fee_rate; + cluster.txs = this.unpack(cluster.txs); + } return cluster; } return; } + /** @asyncUnsafe */ public async $getClustersAt(height: number): Promise { const [clusterRows]: any = await DB.query( ` @@ -102,8 +129,16 @@ class CpfpRepository { ); return clusterRows.map(cluster => { if (cluster?.txs) { - cluster.effectiveFeePerVsize = cluster.fee_rate; - cluster.txs = this.unpack(cluster.txs); + if (cluster.template_algo === TemplateAlgorithm.clusterMempool) { + cluster.templateAlgorithm = TemplateAlgorithm.clusterMempool; + cluster.clusterData = this.unpackCM(cluster.txs); + cluster.txs = cluster.clusterData.txs.map(tx => ({ txid: tx.txid, weight: tx.weight, fee: tx.fee })); + cluster.effectiveFeePerVsize = 0; + } else { + cluster.templateAlgorithm = TemplateAlgorithm.legacy; + cluster.effectiveFeePerVsize = cluster.fee_rate; + cluster.txs = this.unpack(cluster.txs); + } return cluster; } else { return null; @@ -116,16 +151,16 @@ class CpfpRepository { try { const [rows] = await DB.query( ` - SELECT txs, height, root from compact_cpfp_clusters + SELECT txs, height, root, template_algo from compact_cpfp_clusters WHERE height >= ? `, [height] ) as RowDataPacket[][]; if (rows?.length) { for (const clusterToDelete of rows) { - const txs = this.unpack(clusterToDelete?.txs); - for (const tx of txs) { - await transactionRepository.$removeTransaction(tx.txid); + const txids = this.extractTxids(clusterToDelete); + for (const txid of txids) { + await transactionRepository.$removeTransaction(txid); } } } @@ -147,16 +182,16 @@ class CpfpRepository { try { const [rows] = await DB.query( ` - SELECT txs, height, root from compact_cpfp_clusters + SELECT txs, height, root, template_algo from compact_cpfp_clusters WHERE height = ? `, [height] ) as RowDataPacket[][]; if (rows?.length) { for (const clusterToDelete of rows) { - const txs = this.unpack(clusterToDelete?.txs); - for (const tx of txs) { - await transactionRepository.$removeTransaction(tx.txid); + const txids = this.extractTxids(clusterToDelete); + for (const txid of txids) { + await transactionRepository.$removeTransaction(txid); } } } @@ -173,6 +208,13 @@ class CpfpRepository { } } + private extractTxids(row: any): string[] { + if (row.template_algo === TemplateAlgorithm.clusterMempool) { + return this.unpackCM(row.txs).txs.map(tx => tx.txid); + } + return this.unpack(row.txs).map(tx => tx.txid); + } + // insert a dummy row to mark that we've indexed as far as this block public async $insertProgressMarker(height: number): Promise { try { @@ -242,6 +284,117 @@ class CpfpRepository { } } + /** + * Pack cluster mempool data into binary format: + * [num_chunks: uint16] + * Per chunk: [num_txs: uint16] + * Per tx (in linearization order, grouped by chunk): + * [txid: 32 bytes LE] [weight: uint32] [fee: uint64] [num_parents: uint8] [parent_indices: uint8 each] + */ + public packCM(clusterData: CpfpClusterData): ArrayBuffer { + const headerSize = 2; + const chunkHeadersSize = clusterData.chunks.length * 2; + let txDataSize = 0; + for (const tx of clusterData.txs) { + txDataSize += 32 + 4 + 8 + 1 + tx.parents.length; + } + const totalSize = headerSize + chunkHeadersSize + txDataSize; + + const buf = new ArrayBuffer(totalSize); + const view = new DataView(buf); + let offset = 0; + + view.setUint16(offset, clusterData.chunks.length); + offset += 2; + + for (const chunk of clusterData.chunks) { + view.setUint16(offset, chunk.txs.length); + offset += 2; + } + + for (const tx of clusterData.txs) { + for (let x = 0; x < 32; x++) { + view.setUint8(offset + (31 - x), parseInt(tx.txid.slice(x * 2, (x * 2) + 2), 16)); + } + offset += 32; + view.setUint32(offset, tx.weight); + offset += 4; + view.setBigUint64(offset, BigInt(Math.round(tx.fee))); + offset += 8; + view.setUint8(offset, tx.parents.length); + offset += 1; + for (const parentIdx of tx.parents) { + view.setUint8(offset, parentIdx); + offset += 1; + } + } + + return buf; + } + + public unpackCM(buf: Buffer): CpfpClusterData { + if (!buf) { + return { txs: [], chunks: [] }; + } + + try { + const arrayBuffer = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); + const view = new DataView(arrayBuffer); + let offset = 0; + + const numChunks = view.getUint16(offset); + offset += 2; + + const chunkSizes: number[] = []; + for (let i = 0; i < numChunks; i++) { + chunkSizes.push(view.getUint16(offset)); + offset += 2; + } + + const txs: CpfpClusterTx[] = []; + const chunks: { txs: number[], feerate: number }[] = []; + let txIndex = 0; + + for (let chunkIdx = 0; chunkIdx < numChunks; chunkIdx++) { + const chunkTxIndices: number[] = []; + let chunkFee = 0; + let chunkWeight = 0; + + for (let t = 0; t < chunkSizes[chunkIdx]; t++) { + const txid = Array.from(new Uint8Array(arrayBuffer, offset, 32)).reverse().map(b => b.toString(16).padStart(2, '0')).join(''); + offset += 32; + const weight = view.getUint32(offset); + offset += 4; + const fee = Number(view.getBigUint64(offset)); + offset += 8; + const numParents = view.getUint8(offset); + offset += 1; + const parents: number[] = []; + for (let p = 0; p < numParents; p++) { + parents.push(view.getUint8(offset)); + offset += 1; + } + + txs.push({ txid, fee, weight, parents }); + chunkTxIndices.push(txIndex); + chunkFee += fee; + chunkWeight += weight; + txIndex++; + } + + chunks.push({ + txs: chunkTxIndices, + feerate: chunkWeight > 0 ? (chunkFee * 4) / chunkWeight : 0, + }); + } + + return { txs, chunks }; + } catch (e) { + logger.warn(`Failed to unpack CM CPFP cluster. Reason: ` + (e instanceof Error ? e.message : e)); + return { txs: [], chunks: [] }; + } + } + // returns `true` if two sets of CPFP clusters are deeply identical public compareClusters(clustersA: CpfpCluster[], clustersB: CpfpCluster[]): boolean { if (clustersA.length !== clustersB.length) { diff --git a/backend/src/repositories/FlagValueRepository.ts b/backend/src/repositories/FlagValueRepository.ts new file mode 100644 index 000000000..9bedf7913 --- /dev/null +++ b/backend/src/repositories/FlagValueRepository.ts @@ -0,0 +1,145 @@ +import DB from '../database'; +import logger from '../logger'; + +export const INDEXING_PRESETS = [ + {name: 'per block', bucketSize: 1, retentionSpan: 144}, // block span of ~1 day + {name: 'per week', bucketSize: 1008, retentionSpan: -1}, // all + {name: 'per month', bucketSize: 4032, retentionSpan: -1}, // all +]; + +export const INTERVAL_PRESETS = { + '24h': {retentionSpan: 144, bucketSizes: [1]}, + '6m': {retentionSpan: 24192, bucketSizes: [1008, 4032]}, + '1y': {retentionSpan: 48384, bucketSizes: [1008, 4032]}, + '2y': {retentionSpan: 96768, bucketSizes: [1008, 4032]}, + '3y': {retentionSpan: 145152, bucketSizes: [1008, 4032]}, + 'all': {retentionSpan: -1, bucketSizes: [1008, 4032]}, +}; + +class FlagValuesRepository { + /** + * Get the latest indexed day from the database + * + * @asyncSafe */ + public async $getTipAndTailIndexedByBucketSize(bucketSize: number): Promise<{tip: number, tail: number} | null> { + try { + const [rows]: any[] = await DB.query(`SELECT (MAX(start_height) + ?) as tip, MIN(start_height) as tail FROM flag_values WHERE bucket_size = ?`, [bucketSize, bucketSize.toString()]); + if (rows !== null && rows.length > 0 && rows[0].tip !== null && rows[0].tail !== null) { + return rows[0]; + } + } catch (e) { + logger.err(`Cannot get tip and tail indexed from flag_values. Reason: ` + (e instanceof Error ? e.message : e)); + } + return null; + } + + /** + * Get the set of bucket that area already indexed between heights by bucketSize + * + * @asyncSafe */ + public async $getIndexedStartHeights(bucketSize: number, startHeight: number, latestHeight: number): Promise { + try { + const [rows]: any[] = await DB.query( + `SELECT DISTINCT start_height FROM flag_values WHERE bucket_size = ? AND start_height <= ? AND start_height >= ?`, + [bucketSize.toString(), startHeight, latestHeight] + ); + return rows.map(row => row.start_height); + } catch (e) { + logger.err(`Cannot get indexed start heights from flag_values. Reason: ` + (e instanceof Error ? e.message : e)); + } + return []; + } + + public async $saveBatchFlagValues(bucketSize: number, startHeight: number, dataPerFlag: Record>, avgTimestamp: number): Promise { + const params: any[] = []; + const distinctFlags = Object.keys(dataPerFlag); + const avgDate = new Date(Math.round(avgTimestamp) * 1000); + for (const flag of distinctFlags) { + params.push([bucketSize.toString(), startHeight, avgDate, BigInt(flag), dataPerFlag[flag].txCount, dataPerFlag[flag].vSizeTotal]); + } + try { + await DB.query(` + INSERT INTO flag_values (bucket_size, start_height, avg_timestamp, flag_value, tx_count, vsize_total) VALUES ? + ON DUPLICATE KEY UPDATE + avg_timestamp = VALUES(avg_timestamp), tx_count = VALUES(tx_count), vsize_total = VALUES(vsize_total) + `, [params]); + } catch (e) { + logger.debug(`Cannot save flag batched values. Reason: ${e instanceof Error ? e.message : e}`); + throw e; + } + } + + public async $queryTxCountBasedOnMask(mask: bigint, bucketSize: number, op: 'and' | 'or' | 'nor' | undefined, startHeight: number): Promise<{bucketSize: string, startHeight: number, avgTimestamp: number, txCount: number, vSizeTotal: number}[]> { + let flagPredicate = ''; + let params: any[]= []; + switch (op) { + case 'and': { + flagPredicate = 'AND (flag_value & ?) = ?'; + params = [bucketSize.toString(), startHeight, mask, mask]; + } break; + case 'or': { + flagPredicate = 'AND (flag_value & ?) > 0'; + params = [bucketSize.toString(), startHeight, mask]; + } break; + case 'nor': { + flagPredicate = 'AND (flag_value & ?) = 0'; + params = [bucketSize.toString(), startHeight, mask]; + } break; + case undefined: { // op not passed, no boolean operations + params = [bucketSize.toString(), startHeight]; + break; + } + default: throw new Error(`Invalid op '${op}', expected 'and' | 'or' | 'nor' | undefined`); + } + try { + const [rows]: any[] = await DB.query(` + SELECT bucket_size as bucketSize, start_height as startHeight, UNIX_TIMESTAMP(avg_timestamp) as avgTimestamp, + SUM(tx_count) as txCount, SUM(vsize_total) as vSizeTotal + FROM flag_values + WHERE bucket_size = ? AND start_height >= ? ${flagPredicate} + GROUP BY start_height ORDER BY start_height DESC + `, params); + if (rows !== null && rows.length > 0) { + return rows; + } + } catch (e) { + logger.debug(`Cannot get tx counts. Reason: ${e instanceof Error ? e.message : e}`); + } + return []; + } + + /** @asyncSafe */ + public async $deleteFlagValuesBelowHeight(height: number, bucketSize: number): Promise { + try { + await DB.query(`DELETE FROM flag_values WHERE start_height < ? AND bucket_size = ?`, [height, bucketSize.toString()]); + } catch(e) { + logger.err(`Cannot delete flag values below block #${height}. Reason: ` + (e instanceof Error ? e.message : e)); + } + } + + /** @asyncSafe */ + public async $deleteFlagValuesFromHeight(height: number): Promise { + try { + for (const preset of INDEXING_PRESETS) { + const startHeight = Math.floor(height / preset.bucketSize) * preset.bucketSize; + await DB.query(`DELETE FROM flag_values WHERE start_height >= ? AND bucket_size = ?`, [startHeight, preset.bucketSize.toString()]); + } + } catch (e) { + logger.err(`Cannot delete flag values above ${height}. Reason: ` + (e instanceof Error ? e.message : e)); + } + } + + public async $getTotalBlocksIndexedByBucketSize(bucketSize: number): Promise { + try { + const [rows]: any[] = await DB.query(`SELECT (count(distinct start_height) * ?) as total FROM flag_values WHERE bucket_size = ?`, [bucketSize, bucketSize.toString()]); + if (rows !== null && rows.length > 0) { + return rows[0].total; + } + } catch (e) { + logger.err(`Cannot get total blocks indexed in flag_values. Reason: ` + (e instanceof Error ? e.message : e)); + } + return null; + } +} + +export default new FlagValuesRepository(); diff --git a/backend/src/repositories/HashratesRepository.ts b/backend/src/repositories/HashratesRepository.ts index 93aa2d53f..b1297bfda 100644 --- a/backend/src/repositories/HashratesRepository.ts +++ b/backend/src/repositories/HashratesRepository.ts @@ -215,7 +215,7 @@ class HashratesRepository { logger.err('Cannot delete latest hashrates data points. Reason: ' + (e instanceof Error ? e.message : e), logger.tags.mining); } } - + /** * Delete hashrates from the database from timestamp */ diff --git a/backend/src/repositories/NodeRecordsRepository.ts b/backend/src/repositories/NodeRecordsRepository.ts index cf676e35e..cd5856ee1 100644 --- a/backend/src/repositories/NodeRecordsRepository.ts +++ b/backend/src/repositories/NodeRecordsRepository.ts @@ -9,6 +9,7 @@ export interface NodeRecord { } class NodesRecordsRepository { + /** @asyncSafe */ public async $saveRecord(record: NodeRecord): Promise { try { const payloadBytes = Buffer.from(record.payload, 'base64'); @@ -26,6 +27,7 @@ class NodesRecordsRepository { } } + /** @asyncSafe */ public async $getRecordTypes(publicKey: string): Promise { try { const query = ` @@ -40,6 +42,7 @@ class NodesRecordsRepository { } } + /** @asyncSafe */ public async $deleteUnusedRecords(publicKey: string, recordTypes: number[]): Promise { try { let query; diff --git a/backend/src/repositories/NodesSocketsRepository.ts b/backend/src/repositories/NodesSocketsRepository.ts index e85126de4..9e2d28549 100644 --- a/backend/src/repositories/NodesSocketsRepository.ts +++ b/backend/src/repositories/NodesSocketsRepository.ts @@ -9,6 +9,7 @@ export interface NodeSocket { } class NodesSocketsRepository { + /** @asyncSafe */ public async $saveSocket(socket: NodeSocket): Promise { try { await DB.query(` @@ -23,17 +24,19 @@ class NodesSocketsRepository { } } + /** @asyncSafe */ public async $deleteUnusedSockets(publicKey: string, addresses: string[]): Promise { if (addresses.length === 0) { return 0; } try { + const placeholders = addresses.map(() => '?').join(','); const query = ` DELETE FROM nodes_sockets WHERE public_key = ? - AND socket NOT IN (${addresses.map(id => `"${id}"`).join(',')}) + AND socket NOT IN (${placeholders}) `; - const [result] = await DB.query(query, [publicKey]); + const [result] = await DB.query(query, [publicKey, ...addresses]); return result.affectedRows; } catch (e) { logger.err(`Cannot delete unused sockets for ${publicKey} from db. Reason: ` + (e instanceof Error ? e.message : e)); diff --git a/backend/src/repositories/PoolsRepository.ts b/backend/src/repositories/PoolsRepository.ts index c6625775d..4fc3742fd 100644 --- a/backend/src/repositories/PoolsRepository.ts +++ b/backend/src/repositories/PoolsRepository.ts @@ -8,6 +8,7 @@ import { PoolInfo, PoolTag } from '../mempool.interfaces'; class PoolsRepository { /** * Get all pools tagging info + * @asyncUnsafe */ public async $getPools(): Promise { const [rows] = await DB.query('SELECT id, unique_id as uniqueId, name, addresses, regexes, slug FROM pools'); @@ -16,6 +17,7 @@ class PoolsRepository { /** * Get unknown pool tagging info + * @asyncUnsafe */ public async $getUnknownPool(): Promise { let [rows]: any[] = await DB.query('SELECT id, unique_id as uniqueId, name, slug FROM pools where name = "Unknown"'); @@ -28,6 +30,7 @@ class PoolsRepository { /** * Get basic pool info and block count + * @asyncSafe */ public async $getPoolsInfo(interval: string | null = null): Promise { interval = Common.getSqlInterval(interval); @@ -35,6 +38,7 @@ class PoolsRepository { let query = ` SELECT COUNT(blocks.height) As blockCount, + COUNT(CASE WHEN blocks.tx_count = 1 THEN 1 END) AS emptyBlocks, pool_id AS poolId, pools.name AS name, pools.link AS link, @@ -44,7 +48,7 @@ class PoolsRepository { unique_id as poolUniqueId FROM blocks JOIN pools on pools.id = pool_id - LEFT JOIN blocks_audits ON blocks_audits.height = blocks.height + LEFT JOIN blocks_audits ON blocks_audits.hash = blocks.hash WHERE blocks.stale = 0 `; @@ -66,6 +70,7 @@ class PoolsRepository { /** * Get basic pool info and block count between two timestamp + * @asyncSafe */ public async $getPoolsInfoBetween(from: number, to: number): Promise { const query = `SELECT COUNT(height) as blockCount, pools.id as poolId, pools.name as poolName @@ -85,6 +90,7 @@ class PoolsRepository { /** * Get a mining pool info + * @asyncSafe */ public async $getPool(slug: string, parse: boolean = true): Promise { const query = ` @@ -102,7 +108,7 @@ class PoolsRepository { if (parse) { rows[0].regexes = JSON.parse(rows[0].regexes); } - if (['testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK)) { + if (['testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) { rows[0].addresses = []; // pools-v2.json only contains mainnet addresses } else if (parse) { rows[0].addresses = JSON.parse(rows[0].addresses); @@ -117,6 +123,7 @@ class PoolsRepository { /** * Get a mining pool info by its unique id + * @asyncSafe */ public async $getPoolByUniqueId(id: number, parse: boolean = true): Promise { const query = ` @@ -134,7 +141,7 @@ class PoolsRepository { if (parse) { rows[0].regexes = JSON.parse(rows[0].regexes); } - if (['testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK)) { + if (['testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) { rows[0].addresses = []; // pools.json only contains mainnet addresses } else if (parse) { rows[0].addresses = JSON.parse(rows[0].addresses); @@ -149,8 +156,9 @@ class PoolsRepository { /** * Insert a new mining pool in the database - * - * @param pool + * + * @param pool + * @asyncSafe */ public async $insertNewMiningPool(pool: any, slug: string): Promise { try { @@ -166,10 +174,11 @@ class PoolsRepository { /** * Rename an existing mining pool - * + * * @param dbId * @param newSlug - * @param newName + * @param newName + * @asyncSafe */ public async $renameMiningPool(dbId: number, newSlug: string, newName: string): Promise { try { @@ -186,9 +195,10 @@ class PoolsRepository { /** * Update an exisiting mining pool link - * - * @param dbId - * @param newLink + * + * @param dbId + * @param newLink + * @asyncSafe */ public async $updateMiningPoolLink(dbId: number, newLink: string): Promise { try { @@ -206,10 +216,11 @@ class PoolsRepository { /** * Update an existing mining pool addresses or coinbase tags - * - * @param dbId - * @param addresses - * @param regexes + * + * @param dbId + * @param addresses + * @param regexes + * @asyncSafe */ public async $updateMiningPoolTags(dbId: number, addresses: string, regexes: string): Promise { try { diff --git a/backend/src/repositories/PricesRepository.ts b/backend/src/repositories/PricesRepository.ts index e12027a74..bbf9d8caf 100644 --- a/backend/src/repositories/PricesRepository.ts +++ b/backend/src/repositories/PricesRepository.ts @@ -179,7 +179,7 @@ class PricesRepository { prices[currency] = 0; } } - + try { if (!config.FIAT_PRICE.API_KEY) { // Store only the 7 main currencies await DB.query(` @@ -191,8 +191,8 @@ class PricesRepository { await DB.query(` INSERT INTO prices(time, USD, EUR, GBP, CAD, CHF, AUD, JPY, BGN, BRL, CNY, CZK, DKK, HKD, HRK, HUF, IDR, ILS, INR, ISK, KRW, MXN, MYR, NOK, NZD, PHP, PLN, RON, RUB, SEK, SGD, THB, TRY, ZAR) VALUE (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ?, ? , ?, ?, ?, ?, ?, ?, ?, ? , ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? , ? )`, - [time, prices.USD, prices.EUR, prices.GBP, prices.CAD, prices.CHF, prices.AUD, prices.JPY, prices.BGN, prices.BRL, prices.CNY, prices.CZK, prices.DKK, - prices.HKD, prices.HRK, prices.HUF, prices.IDR, prices.ILS, prices.INR, prices.ISK, prices.KRW, prices.MXN, prices.MYR, prices.NOK, prices.NZD, + [time, prices.USD, prices.EUR, prices.GBP, prices.CAD, prices.CHF, prices.AUD, prices.JPY, prices.BGN, prices.BRL, prices.CNY, prices.CZK, prices.DKK, + prices.HKD, prices.HRK, prices.HUF, prices.IDR, prices.ILS, prices.INR, prices.ISK, prices.KRW, prices.MXN, prices.MYR, prices.NOK, prices.NZD, prices.PHP, prices.PLN, prices.RON, prices.RUB, prices.SEK, prices.SGD, prices.THB, prices.TRY, prices.ZAR] ); } @@ -224,6 +224,7 @@ class PricesRepository { } } + /** @asyncUnsafe */ public async $getOldestPriceTime(): Promise { const [oldestRow] = await DB.query(` SELECT UNIX_TIMESTAMP(time) AS time @@ -234,6 +235,7 @@ class PricesRepository { return oldestRow[0] ? oldestRow[0].time : 0; } + /** @asyncUnsafe */ public async $getLatestPriceId(): Promise { const [oldestRow] = await DB.query(` SELECT id @@ -244,6 +246,7 @@ class PricesRepository { return oldestRow[0] ? oldestRow[0].id : null; } + /** @asyncUnsafe */ public async $getLatestPriceTime(): Promise { const [oldestRow] = await DB.query(` SELECT UNIX_TIMESTAMP(time) AS time @@ -254,6 +257,7 @@ class PricesRepository { return oldestRow[0] ? oldestRow[0].time : 0; } + /** @asyncUnsafe */ public async $getPricesTimes(): Promise { const [times] = await DB.query(` SELECT UNIX_TIMESTAMP(time) AS time @@ -267,6 +271,7 @@ class PricesRepository { return times.map(time => time.time); } + /** @asyncUnsafe */ public async $getPricesTimesWithMissingFields(): Promise<{time: number, USD: number, eur_missing: boolean, gbp_missing: boolean, cad_missing: boolean, chf_missing: boolean, aud_missing: boolean, jpy_missing: boolean}[]> { const [times] = await DB.query(` SELECT UNIX_TIMESTAMP(time) AS time, @@ -289,6 +294,7 @@ class PricesRepository { return times as {time: number, USD: number, eur_missing: boolean, gbp_missing: boolean, cad_missing: boolean, chf_missing: boolean, aud_missing: boolean, jpy_missing: boolean}[]; } + /** @asyncUnsafe */ public async $getPricesTimesAndId(): Promise<{time: number, id: number, USD: number}[]> { const [times] = await DB.query(` SELECT @@ -302,6 +308,7 @@ class PricesRepository { return times as {time: number, id: number, USD: number}[]; } + /** @asyncUnsafe */ public async $getLatestConversionRates(): Promise { const [rates] = await DB.query(` SELECT ${ApiPriceFields} @@ -317,12 +324,13 @@ class PricesRepository { return rates[0] as ApiPrice; } + /** @asyncSafe */ public async $getNearestHistoricalPrice(timestamp: number | undefined, currency?: string): Promise { try { const [rates] = await DB.query(` SELECT ${ApiPriceFields} FROM prices - WHERE UNIX_TIMESTAMP(time) < ? + WHERE UNIX_TIMESTAMP(time) <= ? AND USD >= 0 ORDER BY time DESC LIMIT 1`, @@ -341,8 +349,8 @@ class PricesRepository { `); if (!Array.isArray(latestPrices)) { throw Error(`Cannot get single historical price from the database`); - } - + } + // Compute fiat exchange rates let latestPrice = latestPrices[0] as ApiPrice; if (!latestPrice || latestPrice.USD === -1) { @@ -350,8 +358,8 @@ class PricesRepository { } const computeFx = (usd: number, other: number): number => usd <= 0.05 ? 0 : Math.round(Math.max(other, 0) / usd * 100) / 100; - - const exchangeRates: ExchangeRates = config.FIAT_PRICE.API_KEY ? + + const exchangeRates: ExchangeRates = config.FIAT_PRICE.API_KEY ? { USDEUR: computeFx(latestPrice.USD, latestPrice.EUR), USDGBP: computeFx(latestPrice.USD, latestPrice.GBP), @@ -428,6 +436,7 @@ class PricesRepository { } } + /** @asyncSafe */ public async $getHistoricalPrices(currency?: string): Promise { try { const [rates] = await DB.query(` @@ -446,10 +455,10 @@ class PricesRepository { latestPrice = priceUpdater.getEmptyPricesObj(); } - const computeFx = (usd: number, other: number): number => + const computeFx = (usd: number, other: number): number => usd <= 0 ? 0 : Math.round(Math.max(other, 0) / usd * 100) / 100; - - const exchangeRates: ExchangeRates = config.FIAT_PRICE.API_KEY ? + + const exchangeRates: ExchangeRates = config.FIAT_PRICE.API_KEY ? { USDEUR: computeFx(latestPrice.USD, latestPrice.EUR), USDGBP: computeFx(latestPrice.USD, latestPrice.GBP), diff --git a/backend/src/repositories/TransactionRepository.ts b/backend/src/repositories/TransactionRepository.ts index b5067f790..772bfd9ae 100644 --- a/backend/src/repositories/TransactionRepository.ts +++ b/backend/src/repositories/TransactionRepository.ts @@ -1,6 +1,6 @@ import DB from '../database'; import logger from '../logger'; -import { Ancestor, CpfpInfo } from '../mempool.interfaces'; +import { Ancestor, CpfpCluster, CpfpInfo, TemplateAlgorithm } from '../mempool.interfaces'; import cpfpRepository from './CpfpRepository'; class TransactionRepository { @@ -72,6 +72,9 @@ class TransactionRepository { const clusterId = txRows[0].root.toLowerCase(); const cluster = await cpfpRepository.$getCluster(clusterId); if (cluster) { + if (cluster.templateAlgorithm === TemplateAlgorithm.clusterMempool && cluster.clusterData) { + return this.convertCpfpCM(txid, cluster); + } return this.convertCpfp(txid, cluster); } } @@ -116,6 +119,95 @@ class TransactionRepository { effectiveFeePerVsize: cluster.effectiveFeePerVsize, }; } + + private convertCpfpCM(txid: string, cluster: CpfpCluster): CpfpInfo { + const clusterData = cluster.clusterData; + if (!clusterData) { + return { ancestors: [], descendants: [], effectiveFeePerVsize: 0 }; + } + + // Find which chunk this tx belongs to + let txFlatIdx = -1; + let txChunkIndex = -1; + for (let i = 0; i < clusterData.txs.length; i++) { + if (clusterData.txs[i].txid === txid) { + txFlatIdx = i; + break; + } + } + + // Find the chunk containing this tx + for (let chunkIdx = 0; chunkIdx < clusterData.chunks.length; chunkIdx++) { + if (clusterData.chunks[chunkIdx].txs.includes(txFlatIdx)) { + txChunkIndex = chunkIdx; + break; + } + } + + // Derive ancestors/descendants from in-chunk depgraph parents + // For CM, ancestors are the tx's depgraph parents within the cluster, + // descendants are txs that depend on this tx + const ancestors: Ancestor[] = []; + const descendants: Ancestor[] = []; + + if (txFlatIdx >= 0) { + // Build child map + const childMap = new Map(); + for (let i = 0; i < clusterData.txs.length; i++) { + for (const parentIdx of clusterData.txs[i].parents) { + let children = childMap.get(parentIdx); + if (!children) { + children = []; + childMap.set(parentIdx, children); + } + children.push(i); + } + } + + const ancestorSet = new Set(); + const stack = [...clusterData.txs[txFlatIdx].parents]; + while (stack.length) { + const idx = stack.pop(); + if (idx === undefined || ancestorSet.has(idx)) { + continue; + } + ancestorSet.add(idx); + stack.push(...clusterData.txs[idx].parents); + } + + const descendantSet = new Set(); + const dStack = [...(childMap.get(txFlatIdx) || [])]; + while (dStack.length) { + const idx = dStack.pop(); + if (idx === undefined || descendantSet.has(idx)) { + continue; + } + descendantSet.add(idx); + dStack.push(...(childMap.get(idx) || [])); + } + + for (const idx of ancestorSet) { + const tx = clusterData.txs[idx]; + ancestors.push({ txid: tx.txid, weight: tx.weight, fee: tx.fee }); + } + for (const idx of descendantSet) { + const tx = clusterData.txs[idx]; + descendants.push({ txid: tx.txid, weight: tx.weight, fee: tx.fee }); + } + } + + const effectiveFeePerVsize = txChunkIndex >= 0 ? clusterData.chunks[txChunkIndex].feerate : 0; + + return { + ancestors, + descendants, + effectiveFeePerVsize, + cluster: { + ...clusterData, + chunkIndex: txChunkIndex, + }, + }; + } } export default new TransactionRepository(); diff --git a/backend/src/rpc-api/commands.ts b/backend/src/rpc-api/commands.ts index 89ab9cfe6..475200fee 100644 --- a/backend/src/rpc-api/commands.ts +++ b/backend/src/rpc-api/commands.ts @@ -94,4 +94,5 @@ module.exports = { getTxoutSetinfo: 'gettxoutsetinfo', getIndexInfo: 'getindexinfo', testMempoolAccept: 'testmempoolaccept', + tweakFedPegScript: 'tweakfedpegscript', }; diff --git a/backend/src/rpc-api/index.ts b/backend/src/rpc-api/index.ts index 131e1a048..37f4e3e99 100644 --- a/backend/src/rpc-api/index.ts +++ b/backend/src/rpc-api/index.ts @@ -1,61 +1,61 @@ -var commands = require('./commands') -var rpc = require('./jsonrpc') +const commands = require('./commands'); +const rpc = require('./jsonrpc'); // ===----------------------------------------------------------------------===// // JsonRPC // ===----------------------------------------------------------------------===// function Client (opts) { // @ts-ignore - this.rpc = new rpc.JsonRPC(opts) + this.rpc = new rpc.JsonRPC(opts); } // ===----------------------------------------------------------------------===// // cmd // ===----------------------------------------------------------------------===// Client.prototype.cmd = function () { - var args = [].slice.call(arguments) - var cmd = args.shift() + const args = [].slice.call(arguments); + const cmd = args.shift(); - callRpc(cmd, args, this.rpc) -} + callRpc(cmd, args, this.rpc); +}; // ===----------------------------------------------------------------------===// // callRpc // ===----------------------------------------------------------------------===// function callRpc (cmd, args, rpc) { - var fn = args[args.length - 1] + let fn = args[args.length - 1]; // If the last argument is a callback, pop it from the args list if (typeof fn === 'function') { - args.pop() + args.pop(); } else { - fn = function () {} + fn = function () {}; } return rpc.call(cmd, args, function () { - var args = [].slice.call(arguments) + const args = [].slice.call(arguments); // @ts-ignore - args.unshift(null) + args.unshift(null); // @ts-ignore - fn.apply(this, args) + fn.apply(this, args); }, function (err) { - fn(err) - }) + fn(err); + }); } // ===----------------------------------------------------------------------===// // Initialize wrappers // ===----------------------------------------------------------------------===// (function () { - for (var protoFn in commands) { + for (const protoFn in commands) { (function (protoFn) { Client.prototype[protoFn] = function () { - var args = [].slice.call(arguments) - return callRpc(commands[protoFn], args, this.rpc) - } - })(protoFn) + const args = [].slice.call(arguments); + return callRpc(commands[protoFn], args, this.rpc); + }; + })(protoFn); } -})() +})(); // Export! module.exports.Client = Client; diff --git a/backend/src/rpc-api/jsonrpc.ts b/backend/src/rpc-api/jsonrpc.ts index 0bcbdc16c..c810cac0e 100644 --- a/backend/src/rpc-api/jsonrpc.ts +++ b/backend/src/rpc-api/jsonrpc.ts @@ -1,43 +1,43 @@ -var http = require('http') -var https = require('https') +const http = require('http'); +const https = require('https'); import { readFileSync } from 'fs'; -var JsonRPC = function (opts) { +const JsonRPC = function (opts) { // @ts-ignore - this.opts = opts || {} + this.opts = opts || {}; // @ts-ignore - this.http = this.opts.ssl ? https : http -} + this.http = this.opts.ssl ? https : http; +}; JsonRPC.prototype.call = function (method, params) { return new Promise((resolve, reject) => { - var time = Date.now() - var requestJSON + const time = Date.now(); + let requestJSON; if (Array.isArray(method)) { // multiple rpc batch call - requestJSON = [] + requestJSON = []; method.forEach(function (batchCall, i) { requestJSON.push({ id: time + '-' + i, method: batchCall.method, params: batchCall.params - }) - }) + }); + }); } else { // single rpc call requestJSON = { id: time, method: method, params: params - } + }; } // First we encode the request into JSON - requestJSON = JSON.stringify(requestJSON) + requestJSON = JSON.stringify(requestJSON); // prepare request options - var requestOptions = { + const requestOptions = { host: this.opts.host || 'localhost', port: this.opts.port || 8332, method: 'POST', @@ -48,11 +48,11 @@ JsonRPC.prototype.call = function (method, params) { }, agent: false, rejectUnauthorized: this.opts.ssl && this.opts.sslStrict !== false - } + }; if (this.opts.ssl && this.opts.sslCa) { - // @ts-ignore - requestOptions.ca = this.opts.sslCa + // @ts-ignore + requestOptions.ca = this.opts.sslCa; } // use HTTP auth if user and password set @@ -64,61 +64,61 @@ JsonRPC.prototype.call = function (method, params) { requestOptions.auth = this.cachedCookie; } else if (this.opts.user && this.opts.pass) { // @ts-ignore - requestOptions.auth = this.opts.user + ':' + this.opts.pass + requestOptions.auth = this.opts.user + ':' + this.opts.pass; } // Now we'll make a request to the server - var cbCalled = false - var request = this.http.request(requestOptions) + let cbCalled = false; + const request = this.http.request(requestOptions); // start request timeout timer - var reqTimeout = setTimeout(function () { - if (cbCalled) return - cbCalled = true - request.abort() - var err = new Error('ETIMEDOUT') + const reqTimeout = setTimeout(function () { + if (cbCalled) {return;} + cbCalled = true; + request.abort(); + const err = new Error('ETIMEDOUT'); // @ts-ignore - err.code = 'ETIMEDOUT' - reject(err) - }, this.opts.timeout || 30000) + err.code = 'ETIMEDOUT'; + reject(err); + }, this.opts.timeout || 30000); // set additional timeout on socket in case of remote freeze after sending headers request.setTimeout(this.opts.timeout || 30000, function () { - if (cbCalled) return - cbCalled = true - request.abort() - var err = new Error('ESOCKETTIMEDOUT') + if (cbCalled) {return;} + cbCalled = true; + request.abort(); + const err = new Error('ESOCKETTIMEDOUT'); // @ts-ignore - err.code = 'ESOCKETTIMEDOUT' - reject(err) - }) + err.code = 'ESOCKETTIMEDOUT'; + reject(err); + }); request.on('error', function (err) { - if (cbCalled) return - cbCalled = true - clearTimeout(reqTimeout) - reject(err) - }) + if (cbCalled) {return;} + cbCalled = true; + clearTimeout(reqTimeout); + reject(err); + }); request.on('response', (response) => { - clearTimeout(reqTimeout) + clearTimeout(reqTimeout); // We need to buffer the response chunks in a nonblocking way. - var buffer = '' + let buffer = ''; response.on('data', function (chunk) { - buffer = buffer + chunk - }) + buffer = buffer + chunk; + }); // When all the responses are finished, we decode the JSON and // depending on whether it's got a result or an error, we call // emitSuccess or emitError on the promise. response.on('end', () => { - var err + let err; - if (cbCalled) return - cbCalled = true + if (cbCalled) {return;} + cbCalled = true; try { - var decoded = JSON.parse(buffer) + var decoded = JSON.parse(buffer); } catch (e) { // if we authenticated using a cookie and it failed, read the cookie file again if ( @@ -129,19 +129,19 @@ JsonRPC.prototype.call = function (method, params) { } if (response.statusCode !== 200) { - err = new Error('Invalid params, response status code: ' + response.statusCode) - err.code = -32602 - reject(err) + err = new Error('Invalid params, response status code: ' + response.statusCode); + err.code = -32602; + reject(err); } else { - err = new Error('Problem parsing JSON response from server') - err.code = -32603 - reject(err) + err = new Error('Problem parsing JSON response from server'); + err.code = -32603; + reject(err); } - return + return; } if (!Array.isArray(decoded)) { - decoded = [decoded] + decoded = [decoded]; } // iterate over each response, normally there will be just one @@ -149,29 +149,29 @@ JsonRPC.prototype.call = function (method, params) { decoded.forEach(function (decodedResponse, i) { if (decodedResponse.hasOwnProperty('error') && decodedResponse.error != null) { if (reject) { - err = new Error(decodedResponse.error.message || '') + err = new Error(decodedResponse.error.message || ''); if (decodedResponse.error.code) { - err.code = decodedResponse.error.code + err.code = decodedResponse.error.code; } - reject(err) + reject(err); } } else if (decodedResponse.hasOwnProperty('result')) { // @ts-ignore - resolve(decodedResponse.result, response.headers) + resolve(decodedResponse.result, response.headers); } else { if (reject) { - err = new Error(decodedResponse.error.message || '') + err = new Error(decodedResponse.error.message || ''); if (decodedResponse.error.code) { - err.code = decodedResponse.error.code + err.code = decodedResponse.error.code; } - reject(err) + reject(err); } } - }) - }) - }) + }); + }); + }); request.end(requestJSON); }); -} +}; -module.exports.JsonRPC = JsonRPC +module.exports.JsonRPC = JsonRPC; diff --git a/backend/src/sync-assets.ts b/backend/src/sync-assets.ts index f4cc1f266..dfae10d04 100644 --- a/backend/src/sync-assets.ts +++ b/backend/src/sync-assets.ts @@ -10,6 +10,7 @@ const PATH = './'; class SyncAssets { constructor() { } + /** @asyncSafe */ public async syncAssets$() { for (const url of config.MEMPOOL.EXTERNAL_ASSETS) { try { diff --git a/backend/src/tasks/lightning/forensics.service.ts b/backend/src/tasks/lightning/forensics.service.ts index aa88f5bb4..7c6b6f6ac 100644 --- a/backend/src/tasks/lightning/forensics.service.ts +++ b/backend/src/tasks/lightning/forensics.service.ts @@ -23,6 +23,7 @@ class ForensicsService { await this.$runTasks(); } + /** @asyncSafe */ private async $runTasks(): Promise { try { logger.debug(`Running forensics scans`); @@ -36,7 +37,7 @@ class ForensicsService { logger.err('ForensicsService.$runTasks() error: ' + (e instanceof Error ? e.message : e)); } - setTimeout(() => { this.$runTasks(); }, 1000 * config.LIGHTNING.FORENSICS_INTERVAL); + setTimeout(() => { void this.$runTasks(); }, 1000 * config.LIGHTNING.FORENSICS_INTERVAL); } /* @@ -340,6 +341,7 @@ class ForensicsService { } } + /** @asyncSafe */ private async $attributeChannelBalances( prevChannel, openChannel, input: IEsploraApi.Vin, openContribution: number | null = null, initiator: 'remote' | 'local' | null = null, linkedOpenings: boolean = false @@ -449,7 +451,7 @@ class ForensicsService { const initiatorSide = initiator === 'remote' ? prevRemote : prevLocal; prevChannel.closed_by = prevChannel[`node${initiatorSide}_public_key`]; } - + // save changes to the closing channel await channelsApi.$updateClosingInfo(prevChannel); } else { @@ -465,6 +467,7 @@ class ForensicsService { } } + /** @asyncSafe */ async fetchTransaction(txid: string, temp: boolean = false): Promise { let tx = this.txCache[txid]; if (!tx) { @@ -485,6 +488,7 @@ class ForensicsService { // fetches a batch of transactions and adds them to the txCache // the returned list of txs does *not* preserve ordering or number + /** @asyncSafe */ async fetchTransactions(txids, temp: boolean = false): Promise<(IEsploraApi.Transaction | null)[]> { // deduplicate txids const uniqueTxids = [...new Set(txids)]; diff --git a/backend/src/tasks/lightning/network-sync.service.ts b/backend/src/tasks/lightning/network-sync.service.ts index da4eba170..649b70a35 100644 --- a/backend/src/tasks/lightning/network-sync.service.ts +++ b/backend/src/tasks/lightning/network-sync.service.ts @@ -29,6 +29,7 @@ class NetworkSyncService { await this.$runTasks(); } + /** @asyncSafe */ private async $runTasks(): Promise { const taskStartTime = Date.now(); try { @@ -37,7 +38,7 @@ class NetworkSyncService { const networkGraph = await lightningApi.$getNetworkGraph(); if (networkGraph.nodes.length === 0 || networkGraph.edges.length === 0) { logger.info(`LN Network graph is empty, retrying in 10 seconds`, logger.tags.ln); - setTimeout(() => { this.$runTasks(); }, 10000); + setTimeout(() => { void this.$runTasks(); }, 10000); return; } @@ -47,7 +48,7 @@ class NetworkSyncService { await this.$lookUpCreationDateFromChain(); await this.$updateNodeFirstSeen(); await this.$scanForClosedChannels(); - + if (config.MEMPOOL.BACKEND === 'esplora') { // run forensics on new channels only await forensicsService.$runClosedChannelsForensics(true); @@ -57,7 +58,7 @@ class NetworkSyncService { logger.err(`$runTasks() error: ${e instanceof Error ? e.message : e}`, logger.tags.ln); } - setTimeout(() => { this.$runTasks(); }, Math.max(1, (1000 * config.LIGHTNING.GRAPH_REFRESH_INTERVAL) - (Date.now() - taskStartTime))); + setTimeout(() => { void this.$runTasks(); }, Math.max(1, (1000 * config.LIGHTNING.GRAPH_REFRESH_INTERVAL) - (Date.now() - taskStartTime))); } /** @@ -111,7 +112,9 @@ class NetworkSyncService { await nodesApi.$setNodesInactive(graphNodesPubkeys); if (config.MAXMIND.ENABLED) { - $lookupNodeLocation(); + $lookupNodeLocation().catch((e) => { + logger.err(`Error in $lookupNodeLocation: ${e instanceof Error ? e.message : e}`); + }); } } @@ -226,7 +229,7 @@ class NetworkSyncService { if (channels.length > 0) { logger.debug(`Updated ${channels.length} channels' creation date`, logger.tags.ln); - } + } } catch (e) { logger.err(`$lookUpCreationDateFromChain() error: ${e instanceof Error ? e.message : e}`, logger.tags.ln); } diff --git a/backend/src/tasks/lightning/stats-updater.service.ts b/backend/src/tasks/lightning/stats-updater.service.ts index 5d0ac3cfc..5bbe6784d 100644 --- a/backend/src/tasks/lightning/stats-updater.service.ts +++ b/backend/src/tasks/lightning/stats-updater.service.ts @@ -9,17 +9,19 @@ class LightningStatsUpdater { logger.info(`Starting Lightning Stats service`, logger.tags.ln); await this.$runTasks(); - LightningStatsImporter.$run(); + void LightningStatsImporter.$run(); } + /** @asyncSafe */ private async $runTasks(): Promise { await this.$logStatsDaily(); - setTimeout(() => { this.$runTasks(); }, 1000 * config.LIGHTNING.STATS_REFRESH_INTERVAL); + setTimeout(() => { void this.$runTasks(); }, 1000 * config.LIGHTNING.STATS_REFRESH_INTERVAL); } /** * Update the latest entry for each node every config.LIGHTNING.STATS_REFRESH_INTERVAL seconds + * @asyncSafe */ private async $logStatsDaily(): Promise { try { diff --git a/backend/src/tasks/lightning/sync-tasks/funding-tx-fetcher.ts b/backend/src/tasks/lightning/sync-tasks/funding-tx-fetcher.ts index d0b92f42e..04f5c3708 100644 --- a/backend/src/tasks/lightning/sync-tasks/funding-tx-fetcher.ts +++ b/backend/src/tasks/lightning/sync-tasks/funding-tx-fetcher.ts @@ -6,7 +6,7 @@ import logger from '../../../logger'; const fsPromises = promises; -const BLOCKS_CACHE_MAX_SIZE = 100; +const BLOCKS_CACHE_MAX_SIZE = 100; const CACHE_FILE_NAME = config.MEMPOOL.CACHE_DIR + '/ln-funding-txs-cache.json'; class FundingTxFetcher { @@ -28,12 +28,13 @@ class FundingTxFetcher { } } + /** @asyncUnsafe */ async $fetchChannelsFundingTxs(channelIds: string[]): Promise { if (this.running) { return; } this.running = true; - + const globalTimer = new Date().getTime() / 1000; let cacheTimer = new Date().getTime() / 1000; let loggerTimer = new Date().getTime() / 1000; @@ -57,7 +58,9 @@ class FundingTxFetcher { elapsedSeconds = Math.round((new Date().getTime() / 1000) - cacheTimer); if (elapsedSeconds > 60) { logger.debug(`Saving ${Object.keys(this.fundingTxCache).length} funding txs cache into disk`, logger.tags.ln); - fsPromises.writeFile(CACHE_FILE_NAME, JSON.stringify(this.fundingTxCache)); + fsPromises.writeFile(CACHE_FILE_NAME, JSON.stringify(this.fundingTxCache)).catch((e) => { + logger.err(`Error saving funding txs cache to disk: ${e instanceof Error ? e.message : e}`, logger.tags.ln); + }); cacheTimer = new Date().getTime() / 1000; } } @@ -65,20 +68,27 @@ class FundingTxFetcher { if (this.channelNewlyProcessed > 0) { logger.info(`Indexed ${this.channelNewlyProcessed} additional channels funding tx`, logger.tags.ln); logger.debug(`Saving ${Object.keys(this.fundingTxCache).length} funding txs cache into disk`, logger.tags.ln); - fsPromises.writeFile(CACHE_FILE_NAME, JSON.stringify(this.fundingTxCache)); + fsPromises.writeFile(CACHE_FILE_NAME, JSON.stringify(this.fundingTxCache)).catch((e) => { + logger.err(`Error saving funding txs cache to disk: ${e instanceof Error ? e.message : e}`, logger.tags.ln); + }); } this.running = false; } - + + /** @asyncUnsafe */ public async $fetchChannelOpenTx(channelId: string): Promise<{timestamp: number, txid: string, value: number} | null> { channelId = Common.channelIntegerIdToShortId(channelId); + if (!channelId?.length) { + return null; + } + if (this.fundingTxCache[channelId]) { return this.fundingTxCache[channelId]; } - const parts = channelId.split('x'); + const parts = channelId?.split('x') ?? []; if (parts.length < 3) { logger.debug(`Channel ID ${channelId} does not seem valid, should contains at least 3 parts separated by 'x'`, logger.tags.ln); return null; diff --git a/backend/src/tasks/lightning/sync-tasks/node-locations.ts b/backend/src/tasks/lightning/sync-tasks/node-locations.ts index 17974275c..c3d696ff3 100644 --- a/backend/src/tasks/lightning/sync-tasks/node-locations.ts +++ b/backend/src/tasks/lightning/sync-tasks/node-locations.ts @@ -8,6 +8,7 @@ import { ResultSetHeader } from 'mysql2'; import * as IPCheck from '../../../utils/ipcheck.js'; import { Reader } from 'mmdb-lib'; +/** @asyncSafe */ export async function $lookupNodeLocation(): Promise { let loggerTimer = new Date().getTime() / 1000; let progress = 0; @@ -25,7 +26,7 @@ export async function $lookupNodeLocation(): Promise { } catch (e) { } for (const node of nodes) { - const sockets: string[] = node.sockets.split(','); + const sockets: string[] = node.sockets?.split(',') ?? []; for (const socket of sockets) { const ip = socket.substring(0, socket.lastIndexOf(':')).replace('[', '').replace(']', ''); const hasClearnet = [4, 6].includes(net.isIP(ip)); @@ -60,13 +61,13 @@ export async function $lookupNodeLocation(): Promise { if (city && (asn || isp)) { const query = ` - UPDATE nodes SET - as_number = ?, - city_id = ?, - country_id = ?, - subdivision_id = ?, - longitude = ?, - latitude = ?, + UPDATE nodes SET + as_number = ?, + city_id = ?, + country_id = ?, + subdivision_id = ?, + longitude = ?, + latitude = ?, accuracy_radius = ? WHERE public_key = ? `; diff --git a/backend/src/tasks/lightning/sync-tasks/stats-importer.ts b/backend/src/tasks/lightning/sync-tasks/stats-importer.ts index 14ad82d7e..e15efacec 100644 --- a/backend/src/tasks/lightning/sync-tasks/stats-importer.ts +++ b/backend/src/tasks/lightning/sync-tasks/stats-importer.ts @@ -14,6 +14,7 @@ const fsPromises = promises; class LightningStatsImporter { topologiesFolder = config.LIGHTNING.TOPOLOGY_FOLDER; + /** @asyncSafe */ async $run(): Promise { try { const [channels]: any[] = await DB.query('SELECT short_id from channels;'); @@ -33,6 +34,7 @@ class LightningStatsImporter { /** * Generate LN network stats for one day + * @asyncUnsafe */ public async computeNetworkStats(timestamp: number, networkGraph: ILightningApi.NetworkGraph, isHistorical: boolean = false): Promise { @@ -99,7 +101,7 @@ class LightningStatsImporter { const feeRates: number[] = []; const baseFees: number[] = []; const alreadyCountedChannels = {}; - + const [channelsInDbRaw]: any[] = await DB.query(`SELECT short_id FROM channels`); const channelsInDb = {}; for (const channel of channelsInDbRaw) { @@ -108,6 +110,9 @@ class LightningStatsImporter { for (const channel of networkGraph.edges) { const short_id = Common.channelIntegerIdToShortId(channel.channel_id); + if (!short_id?.length) { + continue; + } const tx = await fundingTxFetcher.$fetchChannelOpenTx(short_id); if (!tx) { @@ -142,7 +147,7 @@ class LightningStatsImporter { channels: 0, }; } - + if (!alreadyCountedChannels[short_id]) { capacity += Math.round(tx.value * 100000000); capacities.push(Math.round(tx.value * 100000000)); @@ -159,7 +164,7 @@ class LightningStatsImporter { if (policy && parseInt(policy.fee_rate_milli_msat, 10) < 5000) { avgFeeRate += parseInt(policy.fee_rate_milli_msat, 10); feeRates.push(parseInt(policy.fee_rate_milli_msat, 10)); - } + } if (policy && parseInt(policy.fee_base_msat, 10) < 5000) { avgBaseFee += parseInt(policy.fee_base_msat, 10); baseFees.push(parseInt(policy.fee_base_msat, 10)); @@ -385,7 +390,7 @@ class LightningStatsImporter { totalProcessed++; continue; } - + if (this.isIncorrectSnapshot(timestamp, graph)) { logger.debug(`Ignoring ${this.topologiesFolder}/${filename}, because we defined it as an incorrect snapshot`); ++totalProcessed; @@ -396,7 +401,7 @@ class LightningStatsImporter { logger.info(`Founds a topology file that we did not import. Importing historical lightning stats now.`, logger.tags.ln); logStarted = true; } - + const datestr = `${new Date(timestamp * 1000).toUTCString()} (${timestamp})`; logger.debug(`${datestr}: Found ${graph.nodes.length} nodes and ${graph.edges.length} channels`, logger.tags.ln); @@ -472,7 +477,7 @@ class LightningStatsImporter { fee_rate_milli_msat: edge.fee_proportional_millionths, max_htlc_msat: edge.htlc_maximum_msat, last_update: edge.timestamp, - disabled: false, + disabled: false, }, node2_policy: null, }); @@ -542,7 +547,7 @@ class LightningStatsImporter { // UNIX_TIMESTAMP(added) >= 1591142400 AND UNIX_TIMESTAMP(added) <= 1592006400 OR // UNIX_TIMESTAMP(added) >= 1632787200 AND UNIX_TIMESTAMP(added) <= 1633564800 OR // UNIX_TIMESTAMP(added) >= 1634256000 AND UNIX_TIMESTAMP(added) <= 1645401600 OR - // UNIX_TIMESTAMP(added) >= 1654992000 AND UNIX_TIMESTAMP(added) <= 1661472000 + // UNIX_TIMESTAMP(added) >= 1654992000 AND UNIX_TIMESTAMP(added) <= 1661472000 // ) } } diff --git a/backend/src/tasks/pools-updater.ts b/backend/src/tasks/pools-updater.ts index 05d4ac5fc..a3933b7db 100644 --- a/backend/src/tasks/pools-updater.ts +++ b/backend/src/tasks/pools-updater.ts @@ -19,6 +19,7 @@ class PoolsUpdater { poolsUrl: string = config.MEMPOOL.POOLS_JSON_URL; treeUrl: string = config.MEMPOOL.POOLS_JSON_TREE_URL; + /** @asyncSafe */ public async $startService(): Promise { while ('Bitcoin is still alive') { try { @@ -30,8 +31,9 @@ class PoolsUpdater { } } + /** @asyncSafe */ public async updatePoolsJson(): Promise { - if (['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK) === false || + if (['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK) === false || config.MEMPOOL.ENABLED === false ) { return; @@ -134,6 +136,7 @@ class PoolsUpdater { /** * Fetch our latest pools-v2.json sha from github + * @asyncUnsafe */ private async fetchPoolsSha(): Promise { const response = await this.query(this.treeUrl); @@ -152,6 +155,7 @@ class PoolsUpdater { /** * Http request wrapper + * @asyncUnsafe */ private async query(path): Promise { type axiosOptions = { diff --git a/backend/src/tasks/price-feeds/bitfinex-api.ts b/backend/src/tasks/price-feeds/bitfinex-api.ts index 30b70e9eb..fea9a99ba 100644 --- a/backend/src/tasks/price-feeds/bitfinex-api.ts +++ b/backend/src/tasks/price-feeds/bitfinex-api.ts @@ -3,11 +3,12 @@ import priceUpdater, { PriceFeed, PriceHistory } from '../price-updater'; class BitfinexApi implements PriceFeed { public name: string = 'Bitfinex'; - public currencies: string[] = ['USD', 'EUR', 'GPB', 'JPY']; + public currencies: string[] = ['USD', 'EUR', 'GBP']; public url: string = 'https://api.bitfinex.com/v1/pubticker/BTC'; public urlHist: string = 'https://api-pub.bitfinex.com/v2/candles/trade:{GRANULARITY}:tBTC{CURRENCY}/hist'; + /** @asyncUnsafe */ public async $fetchPrice(currency): Promise { const response = await query(this.url + currency); if (response && response['last_price']) { @@ -17,6 +18,7 @@ class BitfinexApi implements PriceFeed { } } + /** @asyncUnsafe */ public async $fetchRecentPrice(currencies: string[], type: 'hour' | 'day'): Promise { const priceHistory: PriceHistory = {}; diff --git a/backend/src/tasks/price-feeds/bitflyer-api.ts b/backend/src/tasks/price-feeds/bitflyer-api.ts index 72b2e6adf..3f668d325 100644 --- a/backend/src/tasks/price-feeds/bitflyer-api.ts +++ b/backend/src/tasks/price-feeds/bitflyer-api.ts @@ -11,6 +11,7 @@ class BitflyerApi implements PriceFeed { constructor() { } + /** @asyncUnsafe */ public async $fetchPrice(currency): Promise { const response = await query(this.url + currency); if (response && response['ltp']) { diff --git a/backend/src/tasks/price-feeds/coinbase-api.ts b/backend/src/tasks/price-feeds/coinbase-api.ts index d2c6d063a..0ecddfb7d 100644 --- a/backend/src/tasks/price-feeds/coinbase-api.ts +++ b/backend/src/tasks/price-feeds/coinbase-api.ts @@ -11,6 +11,7 @@ class CoinbaseApi implements PriceFeed { constructor() { } + /** @asyncUnsafe */ public async $fetchPrice(currency): Promise { const response = await query(this.url.replace('{CURRENCY}', currency)); if (response && response['data'] && response['data']['amount']) { @@ -20,6 +21,7 @@ class CoinbaseApi implements PriceFeed { } } + /** @asyncUnsafe */ public async $fetchRecentPrice(currencies: string[], type: 'hour' | 'day'): Promise { const priceHistory: PriceHistory = {}; diff --git a/backend/src/tasks/price-feeds/free-currency-api.ts b/backend/src/tasks/price-feeds/free-currency-api.ts index 48e511aa8..cd8e063ea 100644 --- a/backend/src/tasks/price-feeds/free-currency-api.ts +++ b/backend/src/tasks/price-feeds/free-currency-api.ts @@ -56,6 +56,7 @@ class FreeCurrencyApi implements ConversionFeed { constructor() { } + /** @asyncUnsafe */ public async $getQuota(): Promise { const response = await query(`${this.API_URL_PREFIX}status?apikey=${this.API_KEY}`); if (response && response['quotas']) { @@ -64,6 +65,7 @@ class FreeCurrencyApi implements ConversionFeed { return null; } + /** @asyncUnsafe */ public async $fetchLatestConversionRates(): Promise { const response = await query(`${this.API_URL_PREFIX}latest?apikey=${this.API_KEY}`); if (response && response['data']) { @@ -75,6 +77,7 @@ class FreeCurrencyApi implements ConversionFeed { return emptyRates; } + /** @asyncUnsafe */ public async $fetchConversionRates(date: string): Promise { const response = await query(`${this.API_URL_PREFIX}historical?date=${date}&apikey=${this.API_KEY}`, true); if (response && response['data'] && (response['data'][date] || this.PAID)) { diff --git a/backend/src/tasks/price-feeds/gemini-api.ts b/backend/src/tasks/price-feeds/gemini-api.ts index fc86dc0a3..596c9d841 100644 --- a/backend/src/tasks/price-feeds/gemini-api.ts +++ b/backend/src/tasks/price-feeds/gemini-api.ts @@ -11,6 +11,7 @@ class GeminiApi implements PriceFeed { constructor() { } + /** @asyncUnsafe */ public async $fetchPrice(currency): Promise { const response = await query(this.url + currency); if (response && response['last']) { @@ -20,6 +21,7 @@ class GeminiApi implements PriceFeed { } } + /** @asyncUnsafe */ public async $fetchRecentPrice(currencies: string[], type: 'hour' | 'day'): Promise { const priceHistory: PriceHistory = {}; diff --git a/backend/src/tasks/price-feeds/kraken-api.ts b/backend/src/tasks/price-feeds/kraken-api.ts index ebc784c6f..674b0b936 100644 --- a/backend/src/tasks/price-feeds/kraken-api.ts +++ b/backend/src/tasks/price-feeds/kraken-api.ts @@ -21,6 +21,7 @@ class KrakenApi implements PriceFeed { return ticker; } + /** @asyncUnsafe */ public async $fetchPrice(currency): Promise { const response = await query(this.url + currency); const ticker = this.getTicker(currency); @@ -33,6 +34,7 @@ class KrakenApi implements PriceFeed { } } + /** @asyncUnsafe */ public async $fetchRecentPrice(currencies: string[], type: 'hour' | 'day'): Promise { const priceHistory: PriceHistory = {}; @@ -57,6 +59,7 @@ class KrakenApi implements PriceFeed { /** * Fetch weekly price and save it into the database + * @asyncUnsafe */ public async $insertHistoricalPrice(): Promise { const existingPriceTimes = await PricesRepository.$getPricesTimes(); @@ -69,7 +72,7 @@ class KrakenApi implements PriceFeed { // CHF weekly price history goes back to timestamp 1575504000 (December 5, 2019) // AUD weekly price history goes back to timestamp 1591833600 (June 11, 2020) - let priceHistory: any = {}; // map: timestamp -> Prices + const priceHistory: any = {}; // map: timestamp -> Prices for (const currency of this.currencies) { const response = await query(this.urlHist.replace('{GRANULARITY}', '10080') + currency); diff --git a/backend/src/tasks/price-updater.ts b/backend/src/tasks/price-updater.ts index 16a33cfb6..34646e30f 100644 --- a/backend/src/tasks/price-updater.ts +++ b/backend/src/tasks/price-updater.ts @@ -127,14 +127,17 @@ class PriceUpdater { /** * We execute this function before the websocket initialization since * the websocket init is not done asyncronously + * + * @asyncUnsafe */ public async $initializeLatestPriceWithDb(): Promise { this.latestPrices = await PricesRepository.$getLatestConversionRates(); this.latestGoodPrices = JSON.parse(JSON.stringify(this.latestPrices)); } + /** @asyncSafe */ public async $run(): Promise { - if (['testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK)) { + if (['testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) { // Coins have no value on testnet/signet, so we want to always show 0 return; } @@ -210,6 +213,7 @@ class PriceUpdater { /** * Fetch last BTC price from exchanges, average them, and save it in the database once every hour + * @asyncUnsafe */ private async $updatePrice(): Promise { let forceUpdate = false; @@ -303,6 +307,8 @@ class PriceUpdater { * We use MtGox weekly price from July 19, 2010 to September 30, 2013 * We use Kraken weekly price from October 3, 2013 up to last month * We use Kraken hourly price for the past month + * + * @asyncUnsafe */ private async $insertHistoricalPrices(): Promise { const existingPriceTimes = await PricesRepository.$getPricesTimes(); @@ -345,6 +351,8 @@ class PriceUpdater { /** * Find missing hourly prices and insert them in the database * It has a limited backward range and it depends on which API are available + * + * @asyncUnsafe */ private async $insertMissingRecentPrices(type: 'hour' | 'day'): Promise { const existingPriceTimes = await PricesRepository.$getPricesTimes(); @@ -410,6 +418,8 @@ class PriceUpdater { /** * Find missing prices for additional currencies and insert them in the database * We calculate the additional prices from the USD price and the conversion rates + * + * @asyncUnsafe */ private async $insertMissingAdditionalPrices(): Promise { this.lastFailedHistoricalRun = 0; @@ -432,7 +442,7 @@ class PriceUpdater { this.additionalCurrenciesHistoryRunning = true; logger.debug(`Inserting missing historical conversion rates using conversions API to fill ${priceTimesToFill.length} rows`, logger.tags.mining); - let conversionRates: { [timestamp: number]: ConversionRates } = {}; + const conversionRates: { [timestamp: number]: ConversionRates } = {}; let totalInserted = 0; for (let i = 0; i < priceTimesToFill.length; i++) { @@ -464,7 +474,7 @@ class PriceUpdater { } const prices: ApiPrice = this.getEmptyPricesObj(); - + let willInsert = false; for (const conversionCurrency of this.newCurrencies.concat(missingLegacyCurrencies)) { if (conversionRates[yearMonthTimestamp][conversionCurrency] > 0 && priceTime.USD * conversionRates[yearMonthTimestamp][conversionCurrency] < MAX_PRICES[conversionCurrency]) { @@ -474,7 +484,7 @@ class PriceUpdater { prices[conversionCurrency] = 0; } } - + if (willInsert) { await PricesRepository.$saveAdditionalCurrencyPrices(priceTime.time, prices, missingLegacyCurrencies); ++totalInserted; diff --git a/backend/src/utils/axios-query.ts b/backend/src/utils/axios-query.ts index 3a92cd94d..7b01e4369 100644 --- a/backend/src/utils/axios-query.ts +++ b/backend/src/utils/axios-query.ts @@ -5,6 +5,7 @@ import config from '../config'; import logger from '../logger'; import * as https from 'https'; +/** @asyncUnsafe */ export async function query(path, throwOnFail: boolean = false): Promise { type axiosOptions = { headers: { diff --git a/backend/src/utils/bitcoin-script.ts b/backend/src/utils/bitcoin-script.ts index f9755fcb4..2a88477b8 100644 --- a/backend/src/utils/bitcoin-script.ts +++ b/backend/src/utils/bitcoin-script.ts @@ -147,7 +147,7 @@ export { opcodes }; /** extracts m and n from a multisig script (asm), returns nothing if it is not a multisig script */ export function parseMultisigScript(script: string): void | { m: number, n: number } { - if (!script) { + if (!script?.length) { return; } const ops = script.split(' '); @@ -204,7 +204,7 @@ export function getVarIntLength(n: number): number { /** Extracts miner names from a DATUM coinbase transaction */ export function parseDATUMTemplateCreator(coinbaseRaw: string): string[] | null { - let bytes: number[] = []; + const bytes: number[] = []; for (let c = 0; c < coinbaseRaw.length; c += 2) { bytes.push(parseInt(coinbaseRaw.slice(c, c + 2), 16)); } @@ -224,4 +224,27 @@ export function parseDATUMTemplateCreator(coinbaseRaw: string): string[] | null tagString = tagString.replace('\x00', ''); return tagString.split('\x0f').map((name) => name.replace(/[^a-zA-Z0-9 ]/g, '')); -} \ No newline at end of file +} + +/** Extracts miner names from a DMND coinbase transaction */ +export function parseDMNDTemplateCreator(coinbaseRaw: string): string[] { + try { + if (!coinbaseRaw || coinbaseRaw.length % 2 !== 0 || !/^[0-9a-fA-F]+$/.test(coinbaseRaw)) { + return []; + } + + const bytes = Buffer.from(coinbaseRaw, 'hex'); + const blockHeightLength = bytes[0]; + const tagDelimiterIndex = 1 + blockHeightLength; + if (bytes.length <= tagDelimiterIndex || bytes[tagDelimiterIndex] !== 0x00) { + return []; + } + + const tagStart = tagDelimiterIndex + 1; + const tagEnd = bytes.indexOf(0x00, tagStart); + const tags = bytes.subarray(tagStart, tagEnd === -1 ? undefined : tagEnd); + return tags.toString('utf8').split('/').slice(1, -1); + } catch { + return []; + } +} diff --git a/backend/src/utils/file-read.ts b/backend/src/utils/file-read.ts index 11fa26ae2..6c7010ef6 100644 --- a/backend/src/utils/file-read.ts +++ b/backend/src/utils/file-read.ts @@ -2,18 +2,10 @@ import * as fs from 'fs'; import logger from '../logger'; import config from '../config'; -function readFile(filePath: string, bufferSize?: number): string[] { - const fileSize = fs.statSync(filePath).size; - const chunkSize = bufferSize || fileSize; - const fileDescriptor = fs.openSync(filePath, 'r'); - const buffer = Buffer.alloc(chunkSize); - - fs.readSync(fileDescriptor, buffer, 0, chunkSize, fileSize - chunkSize); - fs.closeSync(fileDescriptor); - - const lines = buffer.toString('utf8', 0, chunkSize).split('\n'); - return lines; -} +const CHUNK_SIZE = 1024; +const MAX_WINDOW_SIZE = 50 * CHUNK_SIZE; +const MAX_ITERATIONS = 100; +const SMALL_BATCH_THRESHOLD = 5000; function extractDateFromLogLine(line: string): number | undefined { // Extract time from log: "2021-08-31T12:34:56Z" or "2021-08-31T12:34:56.123456Z" @@ -36,27 +28,377 @@ function extractDateFromLogLine(line: string): number | undefined { return parseFloat(timestamp + '.' + microseconds); } -export function getRecentFirstSeen(hash: string): number | undefined { - const debugLogPath = config.CORE_RPC.DEBUG_LOG_PATH; - if (debugLogPath) { - try { - // Read the last few lines of debug.log - const lines = readFile(debugLogPath, 4096).reverse(); +function readLineAt(fd: number, startPos: number, assumeStartOfLine = false): { line: string; startPos: number; nextPos: number } | null { + const size = fs.fstatSync(fd).size; + if (startPos >= size) { + return null; + } - let bestMatch: number | undefined; - for (const line of lines) { - if (line.includes(`Saw new header hash=${hash}`) || line.includes(`Saw new cmpctblock header hash=${hash}`)) { - return extractDateFromLogLine(line); - } else if (line.includes(`UpdateTip: new best=${hash}`)) { - bestMatch = extractDateFromLogLine(line); + const chunks: Buffer[] = []; + let length = 0; + + if (startPos > 0 && !assumeStartOfLine) { + // Cheap guard: if the previous byte is '\n' we are already aligned + const preceding = Buffer.allocUnsafe(1); + const read = fs.readSync(fd, preceding, 0, 1, startPos - 1); + if (read === 1 && preceding[0] === 0x0a) { + assumeStartOfLine = true; + } + + if (!assumeStartOfLine) { + let searchPos = startPos; + let found = false; + let count = 0; + + while (searchPos > 0 && !found && count < 5) { + const chunkSize = Math.min(CHUNK_SIZE, searchPos); + const buf = Buffer.allocUnsafe(chunkSize); + const bytesRead = fs.readSync(fd, buf, 0, chunkSize, searchPos - chunkSize); + + const nl = buf.lastIndexOf(0x0a, bytesRead - 1); + if (nl !== -1) { + startPos = searchPos - chunkSize + nl + 1; + const slice = buf.subarray(nl + 1, bytesRead); + chunks.unshift(slice); + length += slice.length; + found = true; + } else { + searchPos -= chunkSize; + const slice = buf.subarray(0, bytesRead); + chunks.unshift(slice); + length += slice.length; + count++; } } - - return bestMatch; - } catch (e) { - logger.err(`Cannot parse block first seen time from Core logs. Reason: ` + (e instanceof Error ? e.message : e)); + if (!found && chunks.length > 0) { + startPos = searchPos; + } } } - return undefined; + // Read forward until the end of the line + let pos = startPos + length; + while (pos < size) { + const toRead = Math.min(CHUNK_SIZE, size - pos); + const buf = Buffer.allocUnsafe(toRead); + const n = fs.readSync(fd, buf, 0, toRead, pos); + if (n <= 0) { + break; + } + const slice = buf.subarray(0, n); + const idx = slice.indexOf(0x0a); + if (idx !== -1) { + const finalSlice = slice.subarray(0, idx); + chunks.push(finalSlice); + length += finalSlice.length; + const line = Buffer.concat(chunks, length).toString('utf8'); + const nextPos = pos + idx + 1; + return { line, startPos, nextPos }; + } else { + chunks.push(slice); + length += slice.length; + pos += n; + } + } + if (chunks.length === 0) { + return null; + } + const line = Buffer.concat(chunks, length).toString('utf8'); + return { line, startPos, nextPos: size }; } + +function findTimestampPosition(fd: number, targetTimestamp: number): number { + const size = fs.fstatSync(fd).size; + let low = 0; + let high = size; + let iterations = 0; + + while (low < high && iterations < MAX_ITERATIONS) { + iterations++; + const mid = Math.floor((low + high) / 2); + + let record = readLineAt(fd, mid); + if (!record) { + throw new Error(`Failed to read log line during binary search`); + } + + let ts = extractDateFromLogLine(record.line); + + if (ts === undefined) { // usually caused by empty lines between Core restarts, keep reading forward until we find a correct line + let attempts = 0; + while (attempts < 10) { + record = readLineAt(fd, record.nextPos, true); + if (!record) { + break; // should not happen + } + ts = extractDateFromLogLine(record.line); + if (ts) { + break; + } + attempts++; + } + if (!record || !ts) { + break; // should not happen + } + } + + if (ts < targetTimestamp) { + low = record.nextPos; + } else { + high = record.startPos; + } + } + + return Math.min(low, size); +} + +/** + * Perform a bounded middle-out search for the first seen time of a block within a time window + * Stops when: + * - EOF is reached in either direction + * - A first-seen line is found + * - The time window is exhausted in the respective direction + * - MAX_WINDOW_SIZE bytes have been scanned + * + * @param fd file descriptor of the log file + * @param anchor starting point of the middle-out search + * @param startTimestamp min timestamp of the backward search + * @param endTimestamp max timestamp of the forward search + * @param hash block hash to search for + * @returns timestamp and next file position when the block was first seen, or null if not found + */ +function searchForFirstSeen(fd: number, anchor: number, startTimestamp: number, endTimestamp: number, hash: string): { timestamp: number; nextPos: number; } | null { + const size = fs.fstatSync(fd).size; + const half = Math.floor(MAX_WINDOW_SIZE / 2); + const minOffset = Math.max(0, anchor - half); + const maxOffset = Math.min(size, anchor + half); + + const headerNeedle = `Saw new header hash=${hash}`; + const cmpctNeedle = `Saw new cmpctblock header hash=${hash}`; + const partNeedle = `Initialized PartiallyDownloadedBlock for block ${hash}`; + const updateNeedle = `UpdateTip: new best=${hash}`; + + let fPos = anchor; + let fDone = fPos >= maxOffset; + let bPos = anchor - 1; + let bDone = bPos <= minOffset; + + let bestMatch: { timestamp: number; nextPos: number; } | null = null; + + while (!fDone || !bDone) { + if (!fDone && fPos < maxOffset) { + const forwardLimit = Math.min(maxOffset, fPos + CHUNK_SIZE); + while (fPos < forwardLimit) { + const record = readLineAt(fd, fPos, fPos !== anchor); + if (!record) { + fDone = true; + break; + } + const { line, nextPos } = record; + fPos = nextPos; + + const logTimestamp = extractDateFromLogLine(line); + if (logTimestamp === undefined) { + continue; + } + if (logTimestamp > endTimestamp) { + fDone = true; + break; + } + + if (line.includes(headerNeedle) || line.includes(cmpctNeedle)) { + return { timestamp: logTimestamp, nextPos }; + } + if ((line.includes(partNeedle) || line.includes(updateNeedle)) && !bestMatch) { + bestMatch = { timestamp: logTimestamp, nextPos }; + } + } + } + fDone = fDone || fPos >= maxOffset; + + if (!bDone && bPos > minOffset) { + const backwardLimit = Math.max(minOffset, bPos - CHUNK_SIZE); + while (bPos > backwardLimit) { + const record = readLineAt(fd, bPos); + if (!record) { + bDone = true; + break; + } + const { line, startPos, nextPos } = record; + bPos = startPos - 1; + + const logTimestamp = extractDateFromLogLine(line); + if (logTimestamp === undefined) { + continue; + } + if (logTimestamp < startTimestamp) { + bDone = true; + break; + } + + if (line.includes(headerNeedle) || line.includes(cmpctNeedle)) { + return { timestamp: logTimestamp, nextPos }; + } + if (line.includes(partNeedle) || line.includes(updateNeedle)) { + bestMatch = { timestamp: logTimestamp, nextPos }; + } + } + } + bDone = bDone || bPos <= minOffset; + } + return bestMatch; +} + +export function getBlockFirstSeenFromLogs(hash: string, blockTimestamp: number, oldestLogTimestamp: number): number | null { + const debugLogPath = config.CORE_RPC.DEBUG_LOG_PATH; + if (!debugLogPath || blockTimestamp + 3600 <= oldestLogTimestamp) { + return null; + } + + const fd = fs.openSync(debugLogPath, 'r'); + try { + const EOF = fs.fstatSync(fd).size; + const now = Date.now() / 1000; + const start = blockTimestamp - 7200; + const end = blockTimestamp + 3600 > now ? Number.MAX_SAFE_INTEGER : blockTimestamp + 3600; + const anchor = end === Number.MAX_SAFE_INTEGER ? EOF : findTimestampPosition(fd, blockTimestamp); + return searchForFirstSeen(fd, anchor, start, end, hash)?.timestamp ?? null; + } catch (e) { + logger.debug(`Cannot parse block first seen time from Core logs. Reason: ${e instanceof Error ? e.message : e}`); + return null; + } finally { + fs.closeSync(fd); + } +} + +export function scanLogsForBlocksFirstSeen(blocks: { hash: string; timestamp: number }[], oldestLogTimestamp: number): { hash: string; firstSeen: number | null }[] { + const debugLogPath = config.CORE_RPC.DEBUG_LOG_PATH; + if (!debugLogPath) { + return blocks.map(block => ({ hash: block.hash, firstSeen: null })); + } + + if (blocks.length < SMALL_BATCH_THRESHOLD) { // for small batches, individually binary-search each block's first seen time + return blocks.map(block => ({ hash: block.hash, firstSeen: getBlockFirstSeenFromLogs(block.hash, block.timestamp, oldestLogTimestamp) })); + } + + const firstSeenMap = new Map(); + const missing = new Map(); + + let earliestTimestamp = Number.POSITIVE_INFINITY; + let latestTimestamp = Number.NEGATIVE_INFINITY; + for (const block of blocks) { + firstSeenMap.set(block.hash, null); + + if (block.timestamp + 3600 > oldestLogTimestamp) { + const start = block.timestamp - 7200; + const end = block.timestamp + 3600; + + missing.set(block.hash, { start, end }); + if (start < earliestTimestamp) { + earliestTimestamp = start; + } + if (end > latestTimestamp) { + latestTimestamp = end; + } + } + } + + if (!missing.size) { + return blocks.map(block => ({ hash: block.hash, firstSeen: null })); + } + + const extractHash = (line: string, prefix: string): string | null => { + const idx = line.indexOf(prefix); + if (idx === -1) { + return null; + } + const fragment = line.slice(idx + prefix.length); + const match = fragment.match(/^[0-9a-fA-F]{64}/); + return match ? match[0] : null; + }; + + try { + const fd = fs.openSync(debugLogPath, 'r'); + try { + const size = fs.fstatSync(fd).size; + if (!size) { + return blocks.map(block => ({ hash: block.hash, firstSeen: null })); + } + + for (let record = readLineAt(fd, findTimestampPosition(fd, earliestTimestamp)); record; record = readLineAt(fd, record.nextPos, true)) { + const { line } = record; + const logTimestamp = extractDateFromLogLine(line); + if (!logTimestamp) { + continue; + } + if (logTimestamp > latestTimestamp) { + break; + } + + let hash: string | null = null; + + if (line.includes('Saw new header hash=')) { + hash = extractHash(line, 'Saw new header hash='); + } else if (line.includes('Saw new cmpctblock header hash=')) { + hash = extractHash(line, 'Saw new cmpctblock header hash='); + } else if (line.includes('Initialized PartiallyDownloadedBlock for block ')) { + hash = extractHash(line, 'Initialized PartiallyDownloadedBlock for block '); + } else if (line.includes('UpdateTip: new best=')) { + hash = extractHash(line, 'UpdateTip: new best='); + } + + if (!hash) { + continue; + } + + const window = missing.get(hash); + if (!window) { + continue; + } + + if (logTimestamp < window.start || logTimestamp > window.end) { + continue; + } + + missing.delete(hash); + firstSeenMap.set(hash, logTimestamp); + + if (!missing.size) { + break; + } + } + } finally { + fs.closeSync(fd); + } + } catch (e) { + logger.debug(`Cannot scan blocks first seen from Core logs. Reason: ${e instanceof Error ? e.message : e}`); + } + + return blocks.map(block => ({ hash: block.hash, firstSeen: firstSeenMap.get(block.hash) ?? null })); +} + +export function getOldestLogTimestampFromLogs(filePath: string): number | null { + const fd = fs.openSync(filePath, 'r'); + try { + const size = fs.fstatSync(fd).size; + if (size === 0) { + return null; + } + + let pos = 0; + for (let i = 0; i < 10 && pos < size; i++) { + const record = readLineAt(fd, pos, true); + if (!record) { + break; + } + const ts = extractDateFromLogLine(record.line); + if (ts !== undefined) { + return ts; + } + pos = record.nextPos; + } + return null; + } finally { + fs.closeSync(fd); + } +} \ No newline at end of file diff --git a/backend/src/utils/format.ts b/backend/src/utils/format.ts index 63dc07ae4..ce23bc369 100644 --- a/backend/src/utils/format.ts +++ b/backend/src/utils/format.ts @@ -4,7 +4,7 @@ export function getBytesUnit(bytes: number): string { if (isNaN(bytes) || !isFinite(bytes)) { return 'B'; } - + let unitIndex = 0; while (unitIndex < byteUnits.length && bytes > 1024) { unitIndex++; @@ -18,7 +18,7 @@ export function formatBytes(bytes: number, toUnit: string, skipUnit = false): st if (isNaN(bytes) || !isFinite(bytes)) { return `${bytes}`; } - + let unitIndex = 0; while (unitIndex < byteUnits.length && (toUnit && byteUnits[unitIndex] !== toUnit || (!toUnit && bytes > 1024))) { unitIndex++; diff --git a/backend/src/utils/p-limit.ts b/backend/src/utils/p-limit.ts index 20cead411..c62e2831e 100644 --- a/backend/src/utils/p-limit.ts +++ b/backend/src/utils/p-limit.ts @@ -21,6 +21,8 @@ CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFT OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +import logger from "../logger"; + /* How it works: `this._head` is an instance of `Node` which keeps track of its current value and nests @@ -143,7 +145,9 @@ export default function pLimit(concurrency: number): LimitFunction { const enqueue = (fn, resolve, args) => { queue.enqueue(run.bind(undefined, fn, resolve, args)); - (async () => { + ( + /** @asyncUnsafe */ + async () => { // This function needs to wait until the next microtask before comparing // `activeCount` to `concurrency`, because `activeCount` is updated asynchronously // when the run function is dequeued and called. The comparison in the if-statement @@ -153,7 +157,9 @@ export default function pLimit(concurrency: number): LimitFunction { if (activeCount < concurrency && queue.size > 0) { queue.dequeue()(); } - })(); + })().catch((e) => { + logger.err(`Error in pLimit enqueue: ${e instanceof Error ? e.message : e}`); + }); }; const generator = (fn, ...args) => diff --git a/backend/src/utils/secp256k1.ts b/backend/src/utils/secp256k1.ts index 9e0f6dc3b..b95e1493b 100644 --- a/backend/src/utils/secp256k1.ts +++ b/backend/src/utils/secp256k1.ts @@ -49,7 +49,7 @@ export function isPoint(pointHex: string): boolean { } // Function modified slightly from noble-curves - + // Now we know that pointHex is a 33 or 65 byte hex string. const isCompressed = pointHex.length === 66; diff --git a/backend/testSetup.integration.ts b/backend/testSetup.integration.ts index 74efe871b..4ae0347cb 100644 --- a/backend/testSetup.integration.ts +++ b/backend/testSetup.integration.ts @@ -1,5 +1,5 @@ // Integration test setup - uses real implementations, not mocks -// +// // Note: We don't mock ./mempool-config.json here because: // 1. The MEMPOOL_CONFIG_FILE env var points to mempool-config.test.json // 2. config.ts will load that file via require() if env var is set diff --git a/backend/testSetup.ts b/backend/testSetup.ts index ca51bbbe6..24d42dd3c 100644 --- a/backend/testSetup.ts +++ b/backend/testSetup.ts @@ -1,5 +1,20 @@ jest.mock('./mempool-config.json', () => ({}), { virtual: true }); -jest.mock('./src/logger.ts', () => ({}), { virtual: true }); +jest.mock('./src/logger.ts', () => ({ + emerg: jest.fn(), + alert: jest.fn(), + crit: jest.fn(), + err: jest.fn(), + warn: jest.fn(), + notice: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + updateNetwork: jest.fn(), + tags: { + mining: 'mining', + ln: 'ln', + goggles: 'goggles', + }, +}), { virtual: true }); jest.mock('./src/api/rbf-cache.ts', () => ({}), { virtual: true }); jest.mock('./src/api/mempool.ts', () => ({}), { virtual: true }); jest.mock('./src/api/memory-cache.ts', () => ({}), { virtual: true }); diff --git a/backend/tsconfig.build.json b/backend/tsconfig.build.json index e3d61b71c..225f0944a 100644 --- a/backend/tsconfig.build.json +++ b/backend/tsconfig.build.json @@ -1,6 +1,6 @@ { "extends": "./tsconfig", - "exclude": ["**/*.test.*", "**/__mocks__/*", "**/__tests__/*"], + "exclude": ["**/*.test.*", "**/__mocks__/*", "**/__tests__/*", "**/__e2e__/*"], "compilerOptions": { "types": ["node"] }, diff --git a/contributors/JMoises-XCode.txt b/contributors/JMoises-XCode.txt new file mode 100644 index 000000000..3570cc850 --- /dev/null +++ b/contributors/JMoises-XCode.txt @@ -0,0 +1,3 @@ +I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of February 13, 2026. + +Signed: JMoises-XCode \ No newline at end of file diff --git a/contributors/OscarG673 b/contributors/OscarG673 new file mode 100644 index 000000000..bdbc36381 --- /dev/null +++ b/contributors/OscarG673 @@ -0,0 +1,3 @@ +I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of February 7, 2026. + +Signed: OscarG673 diff --git a/contributors/jramos0.txt b/contributors/jramos0.txt new file mode 100644 index 000000000..6bca26801 --- /dev/null +++ b/contributors/jramos0.txt @@ -0,0 +1,3 @@ +I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of February 6, 2025. + +Signed: jramos0 diff --git a/contributors/kayyrod21.txt b/contributors/kayyrod21.txt new file mode 100644 index 000000000..fd6470420 --- /dev/null +++ b/contributors/kayyrod21.txt @@ -0,0 +1,7 @@ +I, kayyrod21, hereby agree to the Contributor License Agreement of The Mempool Open Source Project. + +Signed, +Kaylee Rodriguez +GitHub: kayyrod21 +Date: 2026-02-05 + diff --git a/contributors/most-improve123.txt b/contributors/most-improve123.txt new file mode 100644 index 000000000..1fa329369 --- /dev/null +++ b/contributors/most-improve123.txt @@ -0,0 +1,3 @@ +I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of January 25, 2022. + +Signed: most-improve123 diff --git a/contributors/rodribp.txt b/contributors/rodribp.txt new file mode 100644 index 000000000..2c466e444 --- /dev/null +++ b/contributors/rodribp.txt @@ -0,0 +1,3 @@ +I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of February 13, 2026. + +Signed: rodribp diff --git a/docker/backend/Dockerfile b/docker/backend/Dockerfile index 942a8f9c8..1d4eb0132 100644 --- a/docker/backend/Dockerfile +++ b/docker/backend/Dockerfile @@ -7,8 +7,8 @@ WORKDIR /build RUN apt-get update && \ apt-get install -y curl ca-certificates && \ - curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \ - apt-get install -y nodejs=22.14.0-1nodesource1 build-essential python3 pkg-config && \ + curl -fsSL https://deb.nodesource.com/setup_24.x | bash - && \ + apt-get install -y nodejs=24.13.0-1nodesource1 build-essential python3 pkg-config && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* @@ -19,7 +19,7 @@ ENV PATH="/usr/local/cargo/bin:$PATH" COPY --from=backend . . COPY --from=rustgbt . ../rust/ ENV FD=/build/rust-gbt -RUN npm install --omit=dev --omit=optional +RUN bash meta/scripts/check-install-scripts.sh && npm ci --omit=dev --omit=optional WORKDIR /build RUN npm run package @@ -28,8 +28,8 @@ FROM rust:1.84-bookworm AS runtime RUN apt-get update && \ apt-get install -y curl ca-certificates && \ - curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \ - apt-get install -y nodejs=22.14.0-1nodesource1 && \ + curl -fsSL https://deb.nodesource.com/setup_24.x | bash - && \ + apt-get install -y nodejs=24.13.0-1nodesource1 && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* diff --git a/docker/backend/mempool-config.json b/docker/backend/mempool-config.json index ee8e329a6..4c3e075ce 100644 --- a/docker/backend/mempool-config.json +++ b/docker/backend/mempool-config.json @@ -29,6 +29,8 @@ "AUDIT": __MEMPOOL_AUDIT__, "RUST_GBT": __MEMPOOL_RUST_GBT__, "LIMIT_GBT": __MEMPOOL_LIMIT_GBT__, + "CLUSTER_MEMPOOL": __MEMPOOL_CLUSTER_MEMPOOL__, + "CLUSTER_MEMPOOL_INDEXING": __MEMPOOL_CLUSTER_MEMPOOL_INDEXING__, "CPFP_INDEXING": __MEMPOOL_CPFP_INDEXING__, "MAX_BLOCKS_BULK_QUERY": __MEMPOOL_MAX_BLOCKS_BULK_QUERY__, "DISK_CACHE_BLOCK_INTERVAL": __MEMPOOL_DISK_CACHE_BLOCK_INTERVAL__, diff --git a/docker/backend/start.sh b/docker/backend/start.sh index ae0bc616f..bb1351b05 100755 --- a/docker/backend/start.sh +++ b/docker/backend/start.sh @@ -33,6 +33,8 @@ __MEMPOOL_POOLS_UPDATE_DELAY__=${MEMPOOL_POOLS_UPDATE_DELAY:=604800} __MEMPOOL_AUDIT__=${MEMPOOL_AUDIT:=false} __MEMPOOL_RUST_GBT__=${MEMPOOL_RUST_GBT:=true} __MEMPOOL_LIMIT_GBT__=${MEMPOOL_LIMIT_GBT:=false} +__MEMPOOL_CLUSTER_MEMPOOL__=${MEMPOOL_CLUSTER_MEMPOOL:=false} +__MEMPOOL_CLUSTER_MEMPOOL_INDEXING__=${MEMPOOL_CLUSTER_MEMPOOL_INDEXING:=false} __MEMPOOL_CPFP_INDEXING__=${MEMPOOL_CPFP_INDEXING:=false} __MEMPOOL_MAX_BLOCKS_BULK_QUERY__=${MEMPOOL_MAX_BLOCKS_BULK_QUERY:=0} __MEMPOOL_DISK_CACHE_BLOCK_INTERVAL__=${MEMPOOL_DISK_CACHE_BLOCK_INTERVAL:=6} @@ -197,6 +199,8 @@ sed -i "s!__MEMPOOL_POOLS_UPDATE_DELAY__!${__MEMPOOL_POOLS_UPDATE_DELAY__}!g" me sed -i "s!__MEMPOOL_AUDIT__!${__MEMPOOL_AUDIT__}!g" mempool-config.json sed -i "s!__MEMPOOL_RUST_GBT__!${__MEMPOOL_RUST_GBT__}!g" mempool-config.json sed -i "s!__MEMPOOL_LIMIT_GBT__!${__MEMPOOL_LIMIT_GBT__}!g" mempool-config.json +sed -i "s!__MEMPOOL_CLUSTER_MEMPOOL__!${__MEMPOOL_CLUSTER_MEMPOOL__}!g" mempool-config.json +sed -i "s!__MEMPOOL_CLUSTER_MEMPOOL_INDEXING__!${__MEMPOOL_CLUSTER_MEMPOOL_INDEXING__}!g" mempool-config.json sed -i "s!__MEMPOOL_CPFP_INDEXING__!${__MEMPOOL_CPFP_INDEXING__}!g" mempool-config.json sed -i "s!__MEMPOOL_MAX_BLOCKS_BULK_QUERY__!${__MEMPOOL_MAX_BLOCKS_BULK_QUERY__}!g" mempool-config.json sed -i "s!__MEMPOOL_DISK_CACHE_BLOCK_INTERVAL__!${__MEMPOOL_DISK_CACHE_BLOCK_INTERVAL__}!g" mempool-config.json diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 4e1094306..ea18199d8 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -12,6 +12,12 @@ services: command: "./wait-for db:3306 --timeout=720 -- nginx -g 'daemon off;'" ports: - 80:8080 + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:8080/ | grep -q ' { }); it('loads the graphs page - mobile', () => { - cy.visit(`${basePath}`) + cy.visit(`${basePath}`); cy.waitForSkeletonGone(); cy.get('#btn-graphs').click().then(() => { cy.viewport('iphone-6'); @@ -113,7 +113,7 @@ describe('Liquid', () => { it('allows searching assets', () => { cy.visit(`${basePath}/assets`); cy.waitForSkeletonGone(); - cy.get('.container-xl input').click().type('Liquid Bitcoin').then(() => { + cy.get('.container-xl input').click().type('Tether USD').then(() => { cy.get('ngb-typeahead-window', { timeout: 30000 }).should('have.length', 1); }); }); diff --git a/frontend/cypress/e2e/liquidtestnet/liquidtestnet.spec.ts b/frontend/cypress/e2e/liquidtestnet/liquidtestnet.spec.ts index b5038f89b..aa3ee6787 100644 --- a/frontend/cypress/e2e/liquidtestnet/liquidtestnet.spec.ts +++ b/frontend/cypress/e2e/liquidtestnet/liquidtestnet.spec.ts @@ -40,7 +40,7 @@ describe('Liquid Testnet', () => { }); it('loads the blocks page', () => { - cy.visit(`${basePath}`) + cy.visit(`${basePath}`); cy.get('#btn-blocks'); cy.waitForSkeletonGone(); }); @@ -58,7 +58,7 @@ describe('Liquid Testnet', () => { }); it('loads the graphs page - mobile', () => { - cy.visit(`${basePath}`) + cy.visit(`${basePath}`); cy.waitForSkeletonGone(); cy.viewport('iphone-6'); cy.get('.tv-only').should('not.exist'); @@ -79,7 +79,7 @@ describe('Liquid Testnet', () => { it('allows searching assets', () => { cy.visit(`${basePath}/assets`); cy.waitForSkeletonGone(); - cy.get('.container-xl input').click().type('Liquid Bitcoin').then(() => { + cy.get('.container-xl input').click().type('Tether USD').then(() => { cy.get('ngb-typeahead-window').should('have.length', 1); }); }); diff --git a/frontend/cypress/e2e/mainnet/calculator.spec.ts b/frontend/cypress/e2e/mainnet/calculator.spec.ts new file mode 100644 index 000000000..6866873fe --- /dev/null +++ b/frontend/cypress/e2e/mainnet/calculator.spec.ts @@ -0,0 +1,290 @@ +import { emitMempoolInfo, receiveWebSocketMessageFromServer } from '../../support/websocket'; + +const calculatorBaseModule = Cypress.env('BASE_MODULE'); + +const MOCK_BTC_PRICE_USD = 123456; +const MOCK_BTC_PRICE_JPY = 11057757; + +describe('Calculator', () => { + beforeEach(() => { + cy.mockMempoolSocketV2(); + cy.visit('/tools/calculator'); + + emitMempoolInfo({ + params: { + command: 'init', + waitForMempoolBlocks: false + } + }); + + cy.get('input[formControlName="bitcoin"]', { timeout: 15000 }).should('be.visible'); + + receiveWebSocketMessageFromServer({ + params: { + message: { + contents: `{"conversions": { "time": 1770429602, "USD": ${MOCK_BTC_PRICE_USD}, "EUR": 59711, "GBP": 51810, "CAD": 96567, "CHF": 54834, "AUD": 100897, "JPY": ${MOCK_BTC_PRICE_JPY} }}` + } + } + }); + + cy.get('.symbol', { timeout: 10000 }).should(($el) => { + expect($el.text().replace(/,/g, '')).to.include(String(MOCK_BTC_PRICE_USD)); + }); + }); + + if (calculatorBaseModule === 'mempool') { + + describe('page load and initial state', () => { + it('loads the calculator page with heading and form', () => { + cy.get('h2').should('contain', 'Calculator'); + cy.contains('Waiting for price feed...').should('not.exist'); + cy.get('input[formControlName="fiat"]').should('be.visible'); + cy.get('input[formControlName="bitcoin"]').should('be.visible'); + cy.get('input[formControlName="satoshis"]').should('be.visible'); + }); + + it('displays the mocked conversion rate in .symbol', () => { + cy.get('.symbol').invoke('text').then((text) => { + expect(text.replace(/,/g, '')).to.include(String(MOCK_BTC_PRICE_USD)); + }); + }); + + it('shows copy buttons for each input', () => { + cy.get('app-clipboard').should('have.length', 3); + cy.get('app-clipboard').each(($el) => { + cy.wrap($el).should('be.visible'); + }); + }); + + it('shows fiat price display and bitcoin visual', () => { + cy.contains('Fiat price last updated').should('be.visible'); + cy.get('.bitcoin-satoshis-text').should('be.visible'); + cy.get('.bitcoin-satoshis-text').should('contain', '₿'); + cy.get('.fiat-text').should('be.visible'); + }); + + it('shows input labels for currency, BTC, and sats', () => { + cy.get('.input-group-text').contains('BTC').should('be.visible'); + cy.get('.input-group-text').contains('sats').should('be.visible'); + }); + }); + + describe('default values', () => { + it('displays 1 BTC with correct sats and fiat', () => { + cy.get('input[formControlName="bitcoin"]').invoke('val').then((btcVal) => { + expect(parseFloat(String(btcVal))).to.equal(1); + }); + cy.get('input[formControlName="satoshis"]').invoke('val').should('equal', '100000000'); + cy.get('input[formControlName="fiat"]').invoke('val').then((fiatVal) => { + const fiat = parseFloat(String(fiatVal).replace(/,/g, '')); + expect(fiat).to.equal(MOCK_BTC_PRICE_USD); + }); + }); + }); + + describe('bitcoin input updates fiat and sats', () => { + it('updates fiat and sats when entering 0.33 BTC', () => { + const expectedFiat = Math.round(MOCK_BTC_PRICE_USD * 0.33 * 100) / 100; // 40740.48 USD + cy.get('input[formControlName="bitcoin"]').clear().type('0.33'); + cy.get('input[formControlName="satoshis"]').invoke('val').should('equal', '33000000'); + cy.get('input[formControlName="fiat"]').invoke('val').then((fiatVal) => { + const fiat = parseFloat(String(fiatVal).replace(/,/g, '')); + expect(fiat).to.equal(expectedFiat); + }); + cy.get('.fiat-text').invoke('text').then((text) => { + expect(text.trim()).to.equal('$40,740.48'); + }); + }); + + it('updates fiat and sats when entering 1 sat (0.00000001 BTC)', () => { + const expectedFiat = (MOCK_BTC_PRICE_USD / 100_000_000 * 100 / 100); + cy.get('input[formControlName="bitcoin"]').clear().type('0.00000001'); + cy.get('input[formControlName="satoshis"]').invoke('val').should('equal', '1'); + cy.get('input[formControlName="fiat"]').invoke('val').then((fiatVal) => { + expect(String(fiatVal)).to.equal(String(expectedFiat)); + }); + cy.get('.fiat-text').invoke('text').then((text) => { + expect(text.trim()).to.equal('$0.00'); + }); + }); + }); + + describe('fiat input updates BTC and sats', () => { + it('updates BTC and sats when entering fiat value', () => { + const fiatAmount = 100; + const expectedBtc = parseFloat((fiatAmount / MOCK_BTC_PRICE_USD).toFixed(8)); + const expectedSats = Math.round((fiatAmount / MOCK_BTC_PRICE_USD) * 100_000_000); + cy.get('input[formControlName="fiat"]').clear().type(String(fiatAmount)); + cy.get('input[formControlName="bitcoin"]').invoke('val').then((btcVal) => { + expect(parseFloat(String(btcVal))).to.equal(expectedBtc); + }); + cy.get('input[formControlName="satoshis"]').invoke('val').then((satsVal) => { + expect(parseInt(String(satsVal), 10)).to.equal(expectedSats); + }); + }); + }); + + describe('satoshis input updates BTC and fiat', () => { + it('updates BTC and fiat when entering 10000 sats', () => { + const satsAmount = 10000; + const expectedFiat = Math.round((satsAmount / 100_000_000) * MOCK_BTC_PRICE_USD * 100) / 100; + cy.get('input[formControlName="satoshis"]').clear().type(String(satsAmount)); + cy.get('input[formControlName="bitcoin"]').invoke('val').should('equal', '0.00010000'); + cy.get('input[formControlName="fiat"]').invoke('val').then((fiatVal) => { + const fiat = parseFloat(String(fiatVal).replace(/,/g, '')); + expect(fiat).to.equal(expectedFiat); + }); + }); + }); + + describe('input sanitization', () => { + it('normalizes comma to dot in fiat input', () => { + cy.get('input[formControlName="fiat"]').clear().type('1,5'); + cy.get('input[formControlName="fiat"]').invoke('val').then((val) => { + expect(String(val)).to.match(/^1\.5/); + }); + }); + + it('limits BTC to 8 decimals', () => { + cy.get('input[formControlName="bitcoin"]').clear().type('1.123456789'); + cy.get('input[formControlName="bitcoin"]').invoke('val').then((val) => { + const parts = String(val).split('.'); + expect(parts.length).to.be.lte(2); + if (parts[1]) { + expect(parts[1].length).to.be.lte(8); + } + }); + }); + + it('strips decimals from satoshis input', () => { + cy.get('input[formControlName="satoshis"]').clear().type('10000.99'); + cy.get('input[formControlName="satoshis"]').invoke('val').then((val) => { + expect(String(val)).not.to.include('.'); + }); + }); + }); + + describe('max supply (21M BTC)', () => { + it('shows warning when entering 21M BTC', () => { + cy.get('input[formControlName="bitcoin"]').clear().type('21000000'); + cy.get('.alert.alert-warning').should('be.visible'); + cy.get('.alert.alert-warning').should('contain', 'Values were capped at the max supply of 21M BTC'); + cy.get('input[formControlName="bitcoin"]').invoke('val').should('equal', '21000000'); + cy.get('input[formControlName="satoshis"]').invoke('val').should('equal', '2100000000000000'); + }); + + it('caps values at max supply', () => { + cy.get('input[formControlName="bitcoin"]').clear().type('25000000'); + cy.get('.alert.alert-warning').should('be.visible'); + cy.get('input[formControlName="bitcoin"]').invoke('val').should('equal', '21000000'); + }); + }); + + describe('clipboard buttons', () => { + it('copy buttons exist and are visible', () => { + cy.get('app-clipboard').should('have.length', 3); + cy.get('app-clipboard button, app-clipboard .btn').each(($btn) => { + cy.wrap($btn).should('be.visible'); + }); + }); + }); + + describe('responsive viewports', () => { + it('calculator is usable on desktop', () => { + cy.viewport('macbook-16'); + cy.get('input[formControlName="bitcoin"]').should('be.visible'); + cy.get('input[formControlName="bitcoin"]').clear().type('1'); + cy.get('input[formControlName="bitcoin"]').invoke('val').should('include', '1'); + }); + + it('calculator is usable on mobile', () => { + cy.viewport('iphone-6'); + cy.get('input[formControlName="bitcoin"]').should('be.visible'); + cy.get('input[formControlName="fiat"]').should('be.visible'); + cy.get('input[formControlName="satoshis"]').should('be.visible'); + }); + }); + + describe('loading state', () => { + it('shows calculator form after price feed loads', () => { + cy.contains('Waiting for price feed...').should('not.exist'); + cy.get('input[formControlName="bitcoin"]').should('be.visible'); + }); + }); + + describe('JPY currency', () => { + beforeEach(() => { + cy.get('app-fiat-selector').scrollIntoView(); + cy.get('app-fiat-selector select').select('JPY'); + cy.get('.symbol', { timeout: 10000 }).should(($el) => { + expect($el.text().replace(/,/g, '')).to.include(String(MOCK_BTC_PRICE_JPY)); + }); + + }); + + it('displays JPY conversion rate in .symbol', () => { + cy.get('.symbol').invoke('text').then((text) => { + expect(text.replace(/,/g, '')).to.include(String(MOCK_BTC_PRICE_JPY)); + }); + cy.get('.fiat-text').invoke('text').then((text) => { + expect(text.trim()).to.equal('¥11,057,757'); + }); + }); + + it('displays 1 BTC with correct sats and fiat in JPY', () => { + cy.get('input[formControlName="bitcoin"]').invoke('val').then((btcVal) => { + expect(parseFloat(String(btcVal))).to.equal(1); + }); + cy.get('input[formControlName="satoshis"]').invoke('val').should('equal', '100000000'); + cy.get('input[formControlName="fiat"]').invoke('val').then((fiatVal) => { + const fiat = parseFloat(String(fiatVal).replace(/,/g, '')); + expect(fiat).to.equal(MOCK_BTC_PRICE_JPY); + }); + }); + + it('updates fiat and sats when entering 0.5 BTC in JPY', () => { + const expectedFiat = Math.round(MOCK_BTC_PRICE_JPY * 0.5) + cy.get('input[formControlName="bitcoin"]').clear().type('0.5'); + cy.get('input[formControlName="satoshis"]').invoke('val').should('equal', '50000000'); + cy.get('input[formControlName="fiat"]').invoke('val').then((fiatVal) => { + const fiat = parseFloat(String(fiatVal).replace(/,/g, '')); + expect(fiat).to.equal(expectedFiat); + }); + cy.get('.fiat-text').invoke('text').then((text) => { + expect(text.trim()).to.equal('¥5,528,879'); + }); + + }); + + it('updates BTC and sats when entering fiat value in JPY', () => { + const fiatAmount = 1000000; + const expectedBtc = parseFloat((fiatAmount / MOCK_BTC_PRICE_JPY).toFixed(8)); + const expectedSats = Math.round((fiatAmount / MOCK_BTC_PRICE_JPY) * 100_000_000); + cy.get('input[formControlName="fiat"]').clear().type(String(fiatAmount)); + cy.get('input[formControlName="bitcoin"]').invoke('val').then((btcVal) => { + expect(parseFloat(String(btcVal))).to.equal(expectedBtc); + }); + cy.get('input[formControlName="satoshis"]').invoke('val').then((satsVal) => { + expect(parseInt(String(satsVal), 10)).to.equal(expectedSats); + }); + }); + + it('updates BTC and fiat when entering 10000 sats in JPY', () => { + const satsAmount = 10000; + const expectedFiat = Math.round((satsAmount / 100_000_000) * MOCK_BTC_PRICE_JPY); + cy.get('input[formControlName="satoshis"]').clear().type(String(satsAmount)); + cy.get('input[formControlName="bitcoin"]').invoke('val').should('equal', '0.00010000'); + cy.get('input[formControlName="fiat"]').invoke('val').then((fiatVal) => { + const fiat = parseFloat(String(fiatVal).replace(/,/g, '')); + expect(fiat).to.equal(expectedFiat); + }); + cy.get('.fiat-text').invoke('text').then((text) => { + expect(text.trim()).to.equal('¥1,106'); + }); + }); + }); + + } else { + it.skip(`Tests cannot be run on the selected BASE_MODULE ${calculatorBaseModule}`); + } +}); diff --git a/frontend/cypress/e2e/mainnet/fiat-currency-formatting.spec.ts b/frontend/cypress/e2e/mainnet/fiat-currency-formatting.spec.ts new file mode 100644 index 000000000..90e17d032 --- /dev/null +++ b/frontend/cypress/e2e/mainnet/fiat-currency-formatting.spec.ts @@ -0,0 +1,275 @@ +const baseModule = Cypress.env('BASE_MODULE'); + +// Helper to select "Fiat" from the BTC/sats/Fiat amount selector dropdown +const selectFiatMode = () => { + cy.get('app-amount-selector').first().scrollIntoView(); + cy.get('app-amount-selector select').first().select('fiat'); +}; + +// Helper to select currency from the fiat selector dropdown +const selectCurrency = (currency: 'USD' | 'JPY') => { + cy.get('app-fiat-selector').first().scrollIntoView(); + cy.get('app-fiat-selector').first().click(); + cy.get('app-fiat-selector select').first().select(currency); +}; + +describe('Fiat Currency Formatting', () => { + + if (baseModule === 'mempool') { + + describe('Dashboard', () => { + beforeEach(() => { + cy.visit('/'); + cy.waitForSkeletonGone(); + selectFiatMode(); + cy.get('.latest-transactions', { timeout: 10000 }).should('exist'); + cy.get('.table-cell-fiat', { timeout: 10000 }).should('have.length.at.least', 1); + }); + + describe('USD formatting', () => { + beforeEach(() => { + selectCurrency('USD'); + }); + + it('displays USD values with correct currency symbol', () => { + cy.get('.table-cell-fiat').eq(1).invoke('text').then((text) => { + const trimmedText = text.trim(); + expect(trimmedText).to.include('$'); + }); + }); + + it('displays USD values with proper decimal format', () => { + cy.get('.table-cell-fiat').eq(1).invoke('text').then((text) => { + const trimmedText = text.trim(); + expect(trimmedText).to.match(/^\$[\d,]+(\.\d{2})?$/); + }); + }); + }); + + describe('JPY formatting', () => { + beforeEach(() => { + selectCurrency('JPY'); + cy.get('.table-cell-fiat').eq(1).should(($el) => { + expect($el.text()).to.include('¥'); + }); + }); + + it('displays JPY values with yen symbol', () => { + cy.get('.table-cell-fiat').eq(1).invoke('text').then((text) => { + const trimmedText = text.trim(); + expect(trimmedText).to.include('¥'); + }); + }); + + it('displays JPY values without decimal places', () => { + cy.get('.table-cell-fiat').eq(1).invoke('text').then((text) => { + const trimmedText = text.trim(); + expect(trimmedText).to.not.match(/\.\d+$/); + }); + }); + + it('formats all JPY values correctly without decimals', () => { + cy.get('.table-cell-fiat').each(($el) => { + const text = $el.text().trim(); + if (text.includes('¥')) { + expect(text).to.not.match(/\.\d+/); + } + }); + }); + }); + + describe('currency switching', () => { + it('correctly formats when switching from USD to JPY', () => { + selectCurrency('USD'); + cy.get('.table-cell-fiat').eq(1).invoke('text').then((usdText) => { + expect(usdText.trim()).to.include('$'); + }); + + selectCurrency('JPY'); + cy.get('.table-cell-fiat').eq(1).should(($el) => { + const text = $el.text().trim(); + expect(text).to.include('¥'); + expect(text).to.not.match(/\.\d+$/); + }); + }); + + it('correctly formats when switching from JPY back to USD', () => { + selectCurrency('JPY'); + cy.get('.table-cell-fiat').eq(1).should(($el) => { + expect($el.text()).to.include('¥'); + }); + + selectCurrency('USD'); + cy.get('.table-cell-fiat').eq(1).should(($el) => { + expect($el.text().trim()).to.include('$'); + }); + }); + }); + }); + + describe('Transaction Page', () => { + beforeEach(() => { + cy.visit('/tx/dd0faea1e9acd5bd812e5130c0912b62d6b63d04bac4558f2e07270ac613a8f2'); + cy.waitForSkeletonGone(); + }); + + it('displays USD fiat values with dollar symbol', () => { + selectCurrency('USD'); + selectFiatMode(); + + cy.get('app-transaction-details .fiat').invoke('text').then((text) => { + expect(text.trim()).to.include('$'); + }); + + cy.get('app-transactions-list .fiat', { timeout: 10000 }).first().invoke('text').then((text) => { + expect(text.trim()).to.include('$'); + }); + }); + + it('displays JPY fiat values without decimals', () => { + selectCurrency('JPY'); + selectFiatMode(); + + + cy.get('app-transaction-details .fiat').invoke('text').then((text) => { + const trimmedText = text.trim(); + expect(trimmedText).to.include('¥'); + expect(trimmedText).to.not.match(/\.\d+/); + }); + + cy.get('app-transactions-list .fiat').first().should(($el) => { + const text = $el.text().trim(); + expect(text).to.include('¥'); + expect(text).to.not.match(/\.\d+/); + }); + }); + }); + + describe('Block Page', () => { + beforeEach(() => { + cy.viewport(1080, 1920); // force taller screen to avoid scrollbars + cy.intercept('/api/v1/blocks/0').as('blocks-0'); + cy.intercept('/api/v1/blocks/10').as('blocks-10'); + cy.intercept('/api/v1/blocks/20').as('blocks-20'); + cy.intercept('/api/txs/outspends*').as('outspends'); + cy.visit('/block/100000'); + }); + + it('displays USD fiat values in block reward with dollar symbol', () => { + cy.wait('@outspends').then(() => { + cy.waitUntil(() => cy.get('.fiat').each(($el) => $el.is(':visible') && $el.text().trim().includes('$'))); + selectCurrency('USD'); + selectFiatMode(); + cy.get('.fiat').each(($el) => { + expect($el.text().trim()).to.include('$'); + }); + }); + }); + + it('displays JPY fiat values without decimals', () => { + cy.wait('@outspends').then(() => { + cy.waitUntil(() => cy.get('.fiat').each(($el) => $el.is(':visible') && $el.text().trim().includes('$'))); + selectCurrency('JPY'); + selectFiatMode(); + cy.get('.fiat').each(($el) => { + expect($el.text().trim()).to.include('¥'); + expect($el.text().trim()).to.not.match(/\.\d+/); + }); + }); + }) + }); + + describe('Address Page', () => { + beforeEach(() => { + cy.visit('/address/1wizaAB16Wrua9V58uNvqktyq2LBLtYso'); + }); + + it('displays USD fiat values with dollar symbol', () => { + selectCurrency('USD'); + selectFiatMode(); + cy.get('app-amount .fiat', { timeout: 10000 }).each(($el) => { + expect($el.text().trim()).to.include('$'); + }); + }); + + it('displays JPY fiat values without decimals', () => { + selectCurrency('JPY'); + selectFiatMode(); + cy.get('app-amount .fiat').first().should(($el) => { + const text = $el.text().trim(); + expect(text).to.include('¥'); + expect(text).to.not.match(/\.\d+/); + }); + }); + }); + + describe('Calculator Page', () => { + beforeEach(() => { + cy.visit('/tools/calculator'); + cy.waitForSkeletonGone(); + cy.get('input[formControlName="bitcoin"]', { timeout: 10000 }).should('be.visible'); + }); + + it('displays USD price with dollar symbol', () => { + selectCurrency('USD'); + cy.get('.symbol').invoke('text').then((text) => { + expect(text.trim()).to.include('$'); + }); + }); + + it('displays JPY price without decimals after switching currency', () => { + selectCurrency('JPY'); + cy.get('.symbol').should(($el) => { + const text = $el.text().trim(); + expect(text).to.include('¥'); + }); + + // Enter a value in BTC to see the fiat conversion + cy.get('input[formControlName="bitcoin"]').clear().type('1'); + + // Check that the fiat output doesn't have decimals for JPY + cy.get('input[formControlName="fiat"]').should(($el) => { + const value = $el.val() as string; + // JPY should not have decimal places in large values + expect(value).to.not.match(/\.\d+$/); + }); + }); + }); + + describe('Mempool Block Tooltip', () => { + beforeEach(() => { + cy.visit('/'); + cy.waitForSkeletonGone(); + selectFiatMode(); + }); + + it('displays USD fiat values in mempool block', () => { + selectCurrency('USD'); + cy.get('#mempool-block-0').scrollIntoView(); + cy.get('#mempool-block-0').click(); + + cy.waitForSkeletonGone(); + + cy.get('app-mempool-block .fiat').each(($el) => { + expect($el.text().trim()).to.include('$'); + }); + }); + + it('displays JPY fiat values in mempool block', () => { + selectCurrency('JPY'); + cy.get('#mempool-block-0').scrollIntoView(); + cy.get('#mempool-block-0').click(); + + cy.waitForSkeletonGone(); + + cy.get('app-mempool-block .fiat').invoke('text').then((text) => { + expect(text.trim()).to.include('¥'); + expect(text).to.not.match(/\.\d+$/); + }); + }); + }); + + } else { + it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`); + } +}); diff --git a/frontend/cypress/e2e/mainnet/mainnet.spec.ts b/frontend/cypress/e2e/mainnet/mainnet.spec.ts index a664f333c..a04803ca0 100644 --- a/frontend/cypress/e2e/mainnet/mainnet.spec.ts +++ b/frontend/cypress/e2e/mainnet/mainnet.spec.ts @@ -14,17 +14,17 @@ const baseModule = Cypress.env('BASE_MODULE'); const areOverlapping = (rect1, rect2) => { // if one rectangle is on the left side of the other if (rect1.right < rect2.left || rect2.right < rect1.left) { - return false + return false; } // if one rectangle is above the other if (rect1.bottom < rect2.top || rect2.bottom < rect1.top) { - return false + return false; } // the rectangles must overlap - return true -} + return true; +}; /** * Returns the bounding rectangle of the first DOM @@ -134,7 +134,7 @@ describe('Mainnet', () => { cy.get('.search-box-container > .form-control').type('A').then(() => { cy.wait('@search-1wizSA'); - cy.get('app-search-results button.dropdown-item').should('have.length', 1) + cy.get('app-search-results button.dropdown-item').should('have.length', 1); }); cy.get('app-search-results button.dropdown-item.active').click().then(() => { diff --git a/frontend/cypress/e2e/mainnet/recent-transactions.spec.ts b/frontend/cypress/e2e/mainnet/recent-transactions.spec.ts new file mode 100644 index 000000000..89ae983e2 --- /dev/null +++ b/frontend/cypress/e2e/mainnet/recent-transactions.spec.ts @@ -0,0 +1,158 @@ +import { mockWebSocketV2, receiveWebSocketMessageFromServer } from '../../support/websocket'; + +const baseModule = Cypress.env('BASE_MODULE'); + +function randomHex(length: number): string { + let result = ''; + const chars = '0123456789abcdef'; + for (let i = 0; i < length; i++) { + result += chars[Math.floor(Math.random() * chars.length)]; + } + return result; +} + +function generateMockTransactions(count: number): any[] { + const txs = []; + for (let i = 0; i < count; i++) { + txs.push({ + txid: randomHex(64), + fee: 1000 + Math.floor(Math.random() * 50000), + vsize: 140 + Math.floor(Math.random() * 500), + value: 10000 + Math.floor(Math.random() * 10000000), + }); + } + return txs; +} + +function sendMockTransactions(count: number): string[] { + const txs = generateMockTransactions(count); + receiveWebSocketMessageFromServer({ + params: { + message: { + contents: JSON.stringify({ transactions: txs }), + }, + }, + }); + return txs.map((tx) => tx.txid); +} + +// Send init fixtures without waitForSkeletonGone (the /txs page has a +// skeleton row that only disappears once transactions fill the limit, +// which won't happen from mempool-info alone). +function initMempoolData(): void { + cy.window({ timeout: 5000 }) + .should((win) => { + expect(win.mockSocket).to.not.be.undefined; + }) + .then((win) => { + cy.readFile('cypress/fixtures/mainnet_live2hchart.json', 'utf-8').then((fixture) => { + win.mockSocket.send(JSON.stringify(fixture)); + }); + cy.readFile('cypress/fixtures/mainnet_mempoolInfo.json', 'utf-8').then((fixture) => { + win.mockSocket.send(JSON.stringify(fixture)); + }); + }); +} + +describe('Recent Transactions Page', () => { + if (baseModule === 'mempool') { + + it('updates the transaction list over time', () => { + mockWebSocketV2(); + cy.visit('/txs'); + initMempoolData(); + + sendMockTransactions(6); + cy.get('[data-cy="transactions-list"] tr').should('have.length.greaterThan', 0); + + const newTxids = sendMockTransactions(6); + const markerTxid = newTxids[0].substring(0, 10); + + cy.get('[data-cy="transactions-list"] tr .table-cell-txid a').should(($rows) => { + const visibleText = [...$rows].map((el) => el.textContent.trim()).join(' '); + expect(visibleText).to.include(markerTxid); + }); + }); + + it('pauses updates when clicking the pause icon', () => { + mockWebSocketV2(); + cy.visit('/txs'); + initMempoolData(); + + sendMockTransactions(6); + cy.get('[data-cy="transactions-list"] tr').should('have.length.greaterThan', 0); + + cy.get('[data-cy="btn-pause"]').click(); + + cy.get('[data-cy="transactions-list"] tr .table-cell-txid a').then(($rows) => { + const pausedTxids = [...$rows].map((el) => el.textContent.trim()); + + const newTxids = sendMockTransactions(6); + const markerTxid = newTxids[0].substring(0, 10); + + // The new txid should NOT appear in the list while paused + cy.wait(1000); + cy.get('[data-cy="transactions-list"] tr .table-cell-txid a').should(($updatedRows) => { + const visibleText = [...$updatedRows].map((el) => el.textContent.trim()).join(' '); + expect(visibleText).to.not.include(markerTxid); + const updatedTxids = [...$updatedRows].map((el) => el.textContent.trim()); + expect(updatedTxids).to.deep.equal(pausedTxids); + }); + }); + }); + + it('caps the list when changing the limit to 10', () => { + mockWebSocketV2(); + cy.visit('/txs'); + initMempoolData(); + + // Send enough batches to fill 50 transactions (6 per batch, need 9 batches) + for (let i = 0; i < 9; i++) { + sendMockTransactions(6); + } + + cy.get('[data-cy="transactions-list"] tr').should('have.length', 50); + cy.get('[data-cy="limit-10"]').click(); + cy.scrollTo('top'); + cy.get('[data-cy="transactions-list"] tr').should('have.length', 10); + }); + + it('shows the new transaction pill when there are new transactions', () => { + mockWebSocketV2(); + cy.visit('/txs'); + initMempoolData(); + + sendMockTransactions(6); + cy.get('[data-cy="transactions-list"] tr').should('have.length.greaterThan', 0); + cy.scrollTo('bottom'); + // Ensure scroll event has fired and auto-pause is active + cy.window().should((win) => { + expect(win.scrollY).to.be.greaterThan(0); + }); + + sendMockTransactions(6); + cy.get('[data-cy="new-tx-pill"]').should('be.visible'); + }); + + it('shows the new transaction pill when there are new transactions and scrolls to the top when clicked', () => { + mockWebSocketV2(); + cy.visit('/txs'); + initMempoolData(); + + sendMockTransactions(6); + cy.get('[data-cy="transactions-list"] tr').should('have.length.greaterThan', 0); + cy.scrollTo('bottom'); + cy.window().should((win) => { + expect(win.scrollY).to.be.greaterThan(0); + }); + + sendMockTransactions(6); + cy.get('[data-cy="new-tx-pill"]').should('be.visible'); + cy.get('[data-cy="new-tx-pill"]').click(); + cy.wait(1000); + cy.window().then((win) => { + expect(win.scrollY).to.be.eq(0); + }); + }); + } +}); diff --git a/frontend/cypress/support/PageIdleDetector.ts b/frontend/cypress/support/PageIdleDetector.ts index ba0cd222f..44a83a73a 100644 --- a/frontend/cypress/support/PageIdleDetector.ts +++ b/frontend/cypress/support/PageIdleDetector.ts @@ -1,6 +1,6 @@ // source: chrisp_68 @ https://stackoverflow.com/questions/50525143/how-do-you-reliably-wait-for-page-idle-in-cypress-io-test export class PageIdleDetector -{ +{ defaultOptions: object = { timeout: 60000 }; public WaitForPageToBeIdle(): void @@ -15,7 +15,7 @@ export class PageIdleDetector { cy.document(options).should((myDocument: any) => { - expect(myDocument.readyState, "WaitForPageToLoad").to.be.oneOf(["interactive", "complete"]); + expect(myDocument.readyState, 'WaitForPageToLoad').to.be.oneOf(['interactive', 'complete']); }); } @@ -23,9 +23,9 @@ export class PageIdleDetector { cy.window(options).should((myWindow: any) => { - if (!!myWindow.angular) + if (myWindow.angular) { - expect(this.NumberOfPendingAngularRequests(myWindow), "WaitForAngularRequestsToComplete").to.have.length(0); + expect(this.NumberOfPendingAngularRequests(myWindow), 'WaitForAngularRequestsToComplete').to.have.length(0); } }); } @@ -34,16 +34,16 @@ export class PageIdleDetector { cy.window(options).should((myWindow: any) => { - if (!!myWindow.angular) + if (myWindow.angular) { - expect(this.AngularRootScopePhase(myWindow), "WaitForAngularDigestCycleToComplete").to.be.null; + expect(this.AngularRootScopePhase(myWindow), 'WaitForAngularDigestCycleToComplete').to.be.null; } }); } public WaitForAnimationsToStop(options: object = this.defaultOptions): void { - cy.get(":animated", options).should("not.exist"); + cy.get(':animated', options).should('not.exist'); } private getInjector(myWindow: any) @@ -58,6 +58,6 @@ export class PageIdleDetector private AngularRootScopePhase(myWindow: any) { - return this.getInjector(myWindow).get("$rootScope").$$phase; + return this.getInjector(myWindow).get('$rootScope').$$phase; } } diff --git a/frontend/cypress/support/commands.ts b/frontend/cypress/support/commands.ts index 2ce198241..99bc5c4d2 100644 --- a/frontend/cypress/support/commands.ts +++ b/frontend/cypress/support/commands.ts @@ -52,18 +52,18 @@ const codes = { ArrowUp: 38, ArrowRight: 39, ArrowDown: 40 -} +}; Cypress.Commands.add('waitForSkeletonGone', () => { cy.waitUntil(() => { return Cypress.$('.skeleton-loader').length === 0; - }, { verbose: true, description: "waitForSkeletonGone", errorMsg: "skeleton loaders never went away", timeout: 15000, interval: 50 }); + }, { verbose: true, description: 'waitForSkeletonGone', errorMsg: 'skeleton loaders never went away', timeout: 15000, interval: 50 }); }); Cypress.Commands.add( - "waitForPageIdle", + 'waitForPageIdle', () => { - console.warn("Waiting for page idle state"); + console.warn('Waiting for page idle state'); const pageIdleDetector = new PageIdleDetector(); pageIdleDetector.WaitForPageToBeIdle(); } @@ -77,7 +77,7 @@ Cypress.Commands.add('mockMempoolSocketV2', () => { mockWebSocketV2(); }); -Cypress.Commands.add('changeNetwork', (network: "testnet" | "testnet4" | "signet" | "liquid" | "mainnet") => { +Cypress.Commands.add('changeNetwork', (network: 'testnet' | 'testnet4' | 'signet' | 'liquid' | 'mainnet') => { cy.get('.dropdown-toggle').click().then(() => { cy.get(`a.${network}`).click().then(() => { cy.waitForPageIdle(); @@ -88,60 +88,60 @@ Cypress.Commands.add('changeNetwork', (network: "testnet" | "testnet4" | "signet // https://github.com/bahmutov/cypress-arrows/blob/8f0303842a343550fbeaf01528d01d1ff213b70c/src/index.js function keydownCommand($el, key) { - const message = `sending the "${key}" keydown event` + const message = `sending the "${key}" keydown event`; const log = Cypress.log({ name: `keydown: ${key}`, message: message, consoleProps: function () { return { Subject: $el - } + }; } - }) + }); - const e = $el.createEvent('KeyboardEvent') + const e = $el.createEvent('KeyboardEvent'); Object.defineProperty(e, 'key', { get: function () { - return key + return key; } - }) + }); Object.defineProperty(e, 'keyCode', { get: function () { - return this.keyCodeVal + return this.keyCodeVal; } - }) + }); Object.defineProperty(e, 'which', { get: function () { - return this.keyCodeVal + return this.keyCodeVal; } - }) - var metaKey = false + }); + const metaKey = false; Object.defineProperty(e, 'metaKey', { get: function () { - return metaKey + return metaKey; } - }) + }); Object.defineProperty(e, 'shiftKey', { get: function () { - return false + return false; } - }) - e.keyCodeVal = codes[key] + }); + e.keyCodeVal = codes[key]; e.initKeyboardEvent('keydown', true, true, - $el.defaultView, false, false, false, false, e.keyCodeVal, e.keyCodeVal) + $el.defaultView, false, false, false, false, e.keyCodeVal, e.keyCodeVal); - $el.dispatchEvent(e) - log.snapshot().end() - return $el + $el.dispatchEvent(e); + log.snapshot().end(); + return $el; } -Cypress.Commands.add('keydown', { prevSubject: "dom" }, keydownCommand) -Cypress.Commands.add('left', { prevSubject: "dom" }, $el => keydownCommand($el, 'ArrowLeft')) -Cypress.Commands.add('right', { prevSubject: "dom" }, $el => keydownCommand($el, 'ArrowRight')) -Cypress.Commands.add('up', { prevSubject: "dom" }, $el => keydownCommand($el, 'ArrowUp')) -Cypress.Commands.add('down', { prevSubject: "dom" }, $el => keydownCommand($el, 'ArrowDown')) +Cypress.Commands.add('keydown', { prevSubject: 'dom' }, keydownCommand); +Cypress.Commands.add('left', { prevSubject: 'dom' }, $el => keydownCommand($el, 'ArrowLeft')); +Cypress.Commands.add('right', { prevSubject: 'dom' }, $el => keydownCommand($el, 'ArrowRight')); +Cypress.Commands.add('up', { prevSubject: 'dom' }, $el => keydownCommand($el, 'ArrowUp')); +Cypress.Commands.add('down', { prevSubject: 'dom' }, $el => keydownCommand($el, 'ArrowDown')); diff --git a/frontend/cypress/support/index.d.ts b/frontend/cypress/support/index.d.ts index 21ffe6a2d..122ce5ed5 100644 --- a/frontend/cypress/support/index.d.ts +++ b/frontend/cypress/support/index.d.ts @@ -6,6 +6,6 @@ declare namespace Cypress { waitForPageIdle(): Chainable mockMempoolSocket(): Chainable mockMempoolSocketV2(): Chainable - changeNetwork(network: "testnet"|"testnet4"|"signet"|"liquid"|"mainnet"): Chainable + changeNetwork(network: 'testnet'|'testnet4'|'signet'|'liquid'|'mainnet'): Chainable } } \ No newline at end of file diff --git a/frontend/cypress/support/websocket.ts b/frontend/cypress/support/websocket.ts index b067cc6e8..811d2d8de 100644 --- a/frontend/cypress/support/websocket.ts +++ b/frontend/cypress/support/websocket.ts @@ -32,7 +32,7 @@ export const mockWebSocketV2 = () => { const winWebSocket = win.WebSocket; cy.stub(win, 'WebSocket').callsFake((url) => { console.log(url); - if ((new URL(url).pathname.indexOf('/sockjs-node/') !== 0)) { + if ((new URL(url).pathname.indexOf('/sockjs-node/') !== 0) && (new URL(url).pathname.indexOf('/ng-cli-ws') !== 0)) { const { server, websocket } = createMock(url); win.mockServer = server; @@ -117,23 +117,28 @@ export const receiveWebSocketMessageFromServer = ({ }; +const MOCK_SOCKET_WAIT_TIMEOUT_MS = 5000; + export const emitMempoolInfo = ({ params }: { params?: any } = {}) => { - cy.window().then((win) => { + cy.window({ timeout: MOCK_SOCKET_WAIT_TIMEOUT_MS }) + .should((win) => { + expect(win.mockSocket, 'mockSocket to be set (app should open WebSocket within timeout)').to.not.be.undefined; + }) + .then((win) => { //TODO: Refactor to take into account different parameterized mocking scenarios switch (params.network) { //TODO: Use network specific mocks - case "signet": - case "testnet": - case "mainnet": + case 'signet': + case 'testnet': + case 'mainnet': default: break; } switch (params.command) { - case "init": { - win.mockSocket.send('{"conversions":{"USD":32365.338815782445}}'); + case 'init': { cy.readFile('cypress/fixtures/mainnet_live2hchart.json', 'utf-8').then((fixture) => { win.mockSocket.send(JSON.stringify(fixture)); }); @@ -142,7 +147,7 @@ export const emitMempoolInfo = ({ }); break; } - case "rbfTransaction": { + case 'rbfTransaction': { cy.readFile('cypress/fixtures/mainnet_rbf.json', 'utf-8').then((fixture) => { win.mockSocket.send(JSON.stringify(fixture)); }); @@ -159,12 +164,16 @@ export const emitMempoolInfo = ({ } }); cy.waitForSkeletonGone(); - return cy.get('#mempool-block-0'); + if (!params.waitForMempoolBlocks) { + return + } else { + return cy.get('#mempool-block-0'); + } }; export const dropWebSocket = (() => { cy.window().then((win) => { - win.mockServer.simulate("error"); + win.mockServer.simulate('error'); }); return cy.wait(500); }); diff --git a/frontend/generate-config.js b/frontend/generate-config.js index 3ae3870e5..4485c2a95 100644 --- a/frontend/generate-config.js +++ b/frontend/generate-config.js @@ -46,6 +46,22 @@ try { throw new Error(e); } +// Inject theme manifest if it exists (created by generate-themes.js) +const THEME_MANIFEST_FILE = 'theme-manifest.json'; +try { + const themeManifest = fs.readFileSync(THEME_MANIFEST_FILE, 'utf-8'); + const themeFiles = JSON.parse(themeManifest); + let indexHtml = fs.readFileSync('src/index.html', 'utf-8'); + const script = ` `; + indexHtml = indexHtml.replace('', `${script}\n`); + fs.writeFileSync('src/index.html', indexHtml); + console.log('Injected theme manifest into src/index.html:', themeFiles); +} catch (e) { + if (e.code !== 'ENOENT') { + console.log('Warning: Could not inject theme manifest:', e.message); + } +} + try { const packageJson = fs.readFileSync('package.json'); packetJsonVersion = JSON.parse(packageJson).version; diff --git a/frontend/generate-themes.js b/frontend/generate-themes.js new file mode 100644 index 000000000..ef8785203 --- /dev/null +++ b/frontend/generate-themes.js @@ -0,0 +1,57 @@ +const { execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); + +const THEMES = ['contrast', 'softsimon', 'bukele', 'nymkappa']; +const STAGING_DIR = path.join(__dirname, '.theme-build'); +const DIST_DIR = path.join(__dirname, 'dist/mempool/browser'); +const MANIFEST_FILE = path.join(__dirname, 'theme-manifest.json'); + +const command = process.argv[2]; + +if (command === 'copy') { + const themeFiles = fs.readdirSync(STAGING_DIR).filter(f => f.endsWith('.css')); + for (const dir of fs.readdirSync(DIST_DIR, { withFileTypes: true })) { + if (dir.isDirectory()) { + for (const file of themeFiles) { + fs.copyFileSync(path.join(STAGING_DIR, file), path.join(DIST_DIR, dir.name, file)); + } + } + } + console.log(`Copied ${themeFiles.length} theme files to all locale directories`); +} else { + fs.rmSync(STAGING_DIR, { recursive: true, force: true }); + fs.mkdirSync(STAGING_DIR, { recursive: true }); + + const manifest = {}; + + for (const theme of THEMES) { + const inputFile = path.join(__dirname, `src/theme-${theme}.scss`); + const tempOutput = path.join(STAGING_DIR, `${theme}.tmp.css`); + + try { + execSync(`npx sass --style=compressed --no-source-map "${inputFile}" "${tempOutput}"`, { + stdio: 'pipe' + }); + } catch (e) { + console.error(`Failed to compile theme-${theme}.scss:`, e.message); + process.exit(1); + } + + const css = fs.readFileSync(tempOutput); + const hash = crypto.createHash('md5').update(css).digest('hex').slice(0, 16); + + const nonHashedFilename = `${theme}.css`; + fs.copyFileSync(tempOutput, path.join(STAGING_DIR, nonHashedFilename)); + + const hashedFilename = `${theme}.${hash}.css`; + fs.renameSync(tempOutput, path.join(STAGING_DIR, hashedFilename)); + + manifest[theme] = hashedFilename; + console.log(`Built ${nonHashedFilename} and ${hashedFilename}`); + } + + fs.writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2)); + console.log('Theme manifest written to theme-manifest.json'); +} diff --git a/frontend/mempool-frontend-config.sample.json b/frontend/mempool-frontend-config.sample.json index 70dc2edba..908c38c74 100644 --- a/frontend/mempool-frontend-config.sample.json +++ b/frontend/mempool-frontend-config.sample.json @@ -2,6 +2,7 @@ "TESTNET_ENABLED": false, "TESTNET4_ENABLED": false, "SIGNET_ENABLED": false, + "REGTEST_ENABLED": false, "LIQUID_ENABLED": false, "LIQUID_TESTNET_ENABLED": false, "MAINNET_ENABLED": true, @@ -21,6 +22,13 @@ "MAINNET_BLOCK_AUDIT_START_HEIGHT": 0, "TESTNET_BLOCK_AUDIT_START_HEIGHT": 0, "SIGNET_BLOCK_AUDIT_START_HEIGHT": 0, + "REGTEST_BLOCK_AUDIT_START_HEIGHT": 0, + "TESTNET4_BLOCK_AUDIT_START_HEIGHT": 0, + "MAINNET_TX_FIRST_SEEN_START_HEIGHT": 0, + "TESTNET_TX_FIRST_SEEN_START_HEIGHT": 0, + "TESTNET4_TX_FIRST_SEEN_START_HEIGHT": 0, + "SIGNET_TX_FIRST_SEEN_START_HEIGHT": 0, + "REGTEST_TX_FIRST_SEEN_START_HEIGHT": 0, "LIGHTNING": false, "HISTORICAL_PRICE": true, "ADDITIONAL_CURRENCIES": false, diff --git a/frontend/meta/scripts/check-install-scripts.sh b/frontend/meta/scripts/check-install-scripts.sh new file mode 100755 index 000000000..89a79489a --- /dev/null +++ b/frontend/meta/scripts/check-install-scripts.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +# +# Audit frontend package-lock.json for unexpected hasInstallScript entries. +# Exits non-zero if any package not on the whitelist has hasInstallScript: true. +# +# Usage (from repo root): +# frontend/meta/scripts/check-install-scripts.sh +# + +set -euo pipefail + +FRONTEND_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +LOCKFILE="${FRONTEND_DIR}/package-lock.json" + +ALLOWED=( + "mempool-frontend" + "@parcel/watcher" + "cypress" + "esbuild" + "fsevents" + "lmdb" + "msgpackr-extract" +) + +is_allowed() { + local pkg="$1" + for allowed in "${ALLOWED[@]}"; do + if [[ "$pkg" == "$allowed" ]]; then + return 0 + fi + done + return 1 +} + +if [[ ! -f "$LOCKFILE" ]]; then + echo "No package-lock.json found at ${LOCKFILE}" + exit 1 +fi + +found_violations=0 + +violations=$( + LOCKFILE_PATH="$LOCKFILE" node - <<'NODE' +const fs = require('fs'); + +const lock = JSON.parse(fs.readFileSync(process.env.LOCKFILE_PATH, 'utf8')); +const pkgs = lock.packages || {}; + +for (const [path, meta] of Object.entries(pkgs)) { + if (meta && meta.hasInstallScript) { + const name = path === '' ? (lock.name || '(root)') : path.replace(/^.*node_modules\//, ''); + console.log(name); + } +} +NODE +) + +while IFS= read -r pkg; do + [[ -z "$pkg" ]] && continue + if ! is_allowed "$pkg"; then + echo "VIOLATION: unauthorized install script in '${pkg}' (${LOCKFILE})" + found_violations=1 + fi +done <<< "$violations" + +if [[ "$found_violations" -eq 1 ]]; then + echo "" + echo "FAILED: Found packages with install scripts not on the whitelist." + echo "If this is a legitimate new dependency, add it to frontend/meta/scripts/check-install-scripts.sh" + exit 1 +else + echo "OK: All frontend install scripts are from whitelisted packages." +fi diff --git a/frontend/meta/scripts/safe-install.sh b/frontend/meta/scripts/safe-install.sh new file mode 100755 index 000000000..bdd8b72c9 --- /dev/null +++ b/frontend/meta/scripts/safe-install.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# +# Safely install frontend npm dependencies by refreshing the lockfile without +# running install scripts, auditing it, then doing the real install. +# +# Usage (from repo root): +# frontend/meta/scripts/safe-install.sh +# + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +FRONTEND_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" +LOCKFILE="${FRONTEND_DIR}/package-lock.json" +RESTORE_LOCKFILE_DONE=0 + +# Back up the lockfile so we can restore on failure +if [[ -f "$LOCKFILE" ]]; then + cp "$LOCKFILE" "${LOCKFILE}.bak" +fi + +restore_lockfile() { + trap - ERR INT TERM + if [[ "${RESTORE_LOCKFILE_DONE}" -eq 1 ]]; then + return + fi + RESTORE_LOCKFILE_DONE=1 + + if [[ -f "${LOCKFILE}.bak" ]]; then + mv "${LOCKFILE}.bak" "$LOCKFILE" + echo "Restored original package-lock.json." + elif [[ -f "$LOCKFILE" ]]; then + rm "$LOCKFILE" + echo "Removed generated package-lock.json." + fi +} + +trap restore_lockfile ERR INT TERM + +echo "==> Refreshing frontend lockfile (--ignore-scripts --package-lock-only)..." +(cd "$FRONTEND_DIR" && npm install --ignore-scripts --package-lock-only --no-audit --no-fund) + +echo "" +echo "==> Auditing lockfile for install scripts..." +bash "${SCRIPT_DIR}/check-install-scripts.sh" + +trap - ERR INT TERM +rm -f "${LOCKFILE}.bak" + +echo "" +echo "==> Installing frontend (npm ci)..." +(cd "$FRONTEND_DIR" && npm ci) + +echo "" +echo "Done." diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 80f3ed2af..380138485 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,50 +1,48 @@ { "name": "mempool-frontend", - "version": "3.3-dev", + "version": "3.4-dev", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "mempool-frontend", - "version": "3.3-dev", + "version": "3.4-dev", "license": "GNU Affero General Public License v3.0", "dependencies": { - "@angular-devkit/build-angular": "^20.3.12", - "@angular/animations": "^20.3.14", - "@angular/cli": "^20.3.12", - "@angular/common": "^20.3.14", - "@angular/compiler": "^20.3.14", - "@angular/core": "^20.3.14", - "@angular/forms": "^20.3.14", - "@angular/localize": "^20.3.14", - "@angular/platform-browser": "^20.3.14", - "@angular/platform-browser-dynamic": "^20.3.14", - "@angular/platform-server": "^20.3.14", - "@angular/router": "^20.3.14", - "@angular/ssr": "^20.3.12", + "@angular-devkit/build-angular": "^20.3.25", + "@angular/animations": "^20.3.25", + "@angular/cli": "^20.3.25", + "@angular/common": "^20.3.25", + "@angular/compiler": "^20.3.25", + "@angular/core": "^20.3.25", + "@angular/forms": "^20.3.25", + "@angular/localize": "^20.3.25", + "@angular/platform-browser": "^20.3.25", + "@angular/platform-browser-dynamic": "^20.3.25", + "@angular/platform-server": "^20.3.25", + "@angular/router": "^20.3.25", + "@angular/ssr": "^20.3.25", "@fortawesome/angular-fontawesome": "^3.0.0", "@fortawesome/fontawesome-common-types": "~6.7.2", "@fortawesome/fontawesome-svg-core": "~6.7.2", "@fortawesome/free-solid-svg-icons": "~6.7.2", "@ng-bootstrap/ng-bootstrap": "^19.0.0", + "@noble/secp256k1": "^3.1.0", "@types/qrcode": "~1.5.0", - "bootstrap": "~4.6.2", + "bootstrap": "~5.3.8", "clipboard": "^2.0.11", - "cypress": "^15.7.0", "domino": "^2.1.6", - "echarts": "~5.4.0", - "esbuild": "^0.25.8", + "echarts": "~6.1.0", "ngx-echarts": "~20.0.2", "ngx-infinite-scroll": "^20.0.0", "qrcode": "1.5.1", "rxjs": "~7.8.1", - "tlite": "^0.1.9", "tslib": "~2.8.0", "zone.js": "~0.15.1" }, "devDependencies": { - "@angular/compiler-cli": "^20.3.14", - "@angular/language-service": "^20.3.14", + "@angular/compiler-cli": "^20.3.25", + "@angular/language-service": "^20.3.25", "@types/node": "^24.9.2", "@typescript-eslint/eslint-plugin": "^8.46.2", "@typescript-eslint/parser": "^8.46.2", @@ -58,7 +56,7 @@ }, "optionalDependencies": { "@cypress/schematic": "^2.5.0", - "cypress": "^15.7.0", + "cypress": "^15.16.0", "cypress-fail-on-console-error": "~5.1.1", "cypress-wait-until": "^3.0.1", "mock-socket": "~9.3.1", @@ -282,12 +280,12 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.2003.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2003.12.tgz", - "integrity": "sha512-5H40lAFF4CKY32C4HOp6bTlOF1f4WsGCwe7FjFQp9A+T7yoCBiHpIWt2JKTwV4sBoTKVDZOnuf0GG+UVKjQT4A==", + "version": "0.2003.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2003.25.tgz", + "integrity": "sha512-39pTqt4wSmpD1WeCee46oSGXRh6TR1PFd9GZEwyZoMvBTMs8mE2sXGxUgd4Qyi5CkQ1XnCM5XfgJcjUWUQRoGg==", "license": "MIT", "dependencies": { - "@angular-devkit/core": "20.3.12", + "@angular-devkit/core": "20.3.25", "rxjs": "7.8.2" }, "engines": { @@ -297,15 +295,15 @@ } }, "node_modules/@angular-devkit/architect/node_modules/@angular-devkit/core": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.12.tgz", - "integrity": "sha512-ReFxd/UOoVDr3+kIUjmYILQZF89qg62POdY7a7OqBH7plmInFlYVSEDouJvGqj3LVCPiqTk2ZOSChbhS/eLxXA==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.25.tgz", + "integrity": "sha512-pSfeWEoS1y9zxqTOw0Etj2NXkRmp/aU52wdnyq3KqVA9cJtgxtlOHlBBNswTil4dReojEy+0K1xJm+mLuWuLxA==", "license": "MIT", "dependencies": { - "ajv": "8.17.1", + "ajv": "8.18.0", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", - "picomatch": "4.0.3", + "picomatch": "4.0.4", "rxjs": "7.8.2", "source-map": "0.7.6" }, @@ -324,9 +322,9 @@ } }, "node_modules/@angular-devkit/architect/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -360,7 +358,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "license": "MIT", "optional": true, "peer": true, "dependencies": { @@ -380,9 +377,9 @@ "license": "MIT" }, "node_modules/@angular-devkit/architect/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", "engines": { "node": ">=12" @@ -395,7 +392,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "license": "MIT", "optional": true, "peer": true, "engines": { @@ -416,16 +412,16 @@ } }, "node_modules/@angular-devkit/build-angular": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-20.3.12.tgz", - "integrity": "sha512-HPepPbJA5vprYTWJaSCfpk0s1bPT6Ui6VjFOSb9oY+p9iq+MGkuB1I+swNcRcMLttyMD+FpbMd27F8jSeX5XVw==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-20.3.25.tgz", + "integrity": "sha512-jp2sbJhbVRT65RbGENY/lz3Z0W0D50Af3xzENmLDLBVp9uhlpIPSTC9LG4CEPV7T6H5Oq6ymHKM+OO5WwTlHHA==", "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2003.12", - "@angular-devkit/build-webpack": "0.2003.12", - "@angular-devkit/core": "20.3.12", - "@angular/build": "20.3.12", + "@angular-devkit/architect": "0.2003.25", + "@angular-devkit/build-webpack": "0.2003.25", + "@angular-devkit/core": "20.3.25", + "@angular/build": "20.3.25", "@babel/core": "7.28.3", "@babel/generator": "7.28.3", "@babel/helper-annotate-as-pure": "7.27.3", @@ -436,14 +432,14 @@ "@babel/preset-env": "7.28.3", "@babel/runtime": "7.28.3", "@discoveryjs/json-ext": "0.6.3", - "@ngtools/webpack": "20.3.12", + "@ngtools/webpack": "20.3.25", "ansi-colors": "4.1.3", "autoprefixer": "10.4.21", "babel-loader": "10.0.0", "browserslist": "^4.21.5", - "copy-webpack-plugin": "13.0.1", + "copy-webpack-plugin": "14.0.0", "css-loader": "7.1.2", - "esbuild-wasm": "0.25.9", + "esbuild-wasm": "0.28.0", "fast-glob": "3.3.3", "http-proxy-middleware": "3.0.5", "istanbul-lib-instrument": "6.0.3", @@ -456,9 +452,9 @@ "mini-css-extract-plugin": "2.9.4", "open": "10.2.0", "ora": "8.2.0", - "picomatch": "4.0.3", + "picomatch": "4.0.4", "piscina": "5.1.3", - "postcss": "8.5.6", + "postcss": "8.5.12", "postcss-loader": "8.1.1", "resolve-url-loader": "5.0.0", "rxjs": "7.8.2", @@ -470,7 +466,7 @@ "terser": "5.43.1", "tree-kill": "1.2.2", "tslib": "2.8.1", - "webpack": "5.101.2", + "webpack": "5.105.0", "webpack-dev-middleware": "7.4.2", "webpack-dev-server": "5.2.2", "webpack-merge": "6.0.1", @@ -482,7 +478,7 @@ "yarn": ">= 1.13.0" }, "optionalDependencies": { - "esbuild": "0.25.9" + "esbuild": "0.28.0" }, "peerDependencies": { "@angular/compiler-cli": "^20.0.0", @@ -491,7 +487,7 @@ "@angular/platform-browser": "^20.0.0", "@angular/platform-server": "^20.0.0", "@angular/service-worker": "^20.0.0", - "@angular/ssr": "^20.3.12", + "@angular/ssr": "^20.3.25", "@web/test-runner": "^0.20.0", "browser-sync": "^3.0.2", "jest": "^29.5.0 || ^30.2.0", @@ -548,15 +544,15 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.12.tgz", - "integrity": "sha512-ReFxd/UOoVDr3+kIUjmYILQZF89qg62POdY7a7OqBH7plmInFlYVSEDouJvGqj3LVCPiqTk2ZOSChbhS/eLxXA==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.25.tgz", + "integrity": "sha512-pSfeWEoS1y9zxqTOw0Etj2NXkRmp/aU52wdnyq3KqVA9cJtgxtlOHlBBNswTil4dReojEy+0K1xJm+mLuWuLxA==", "license": "MIT", "dependencies": { - "ajv": "8.17.1", + "ajv": "8.18.0", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", - "picomatch": "4.0.3", + "picomatch": "4.0.4", "rxjs": "7.8.2", "source-map": "0.7.6" }, @@ -574,10 +570,426 @@ } } }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -611,7 +1023,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "license": "MIT", "optional": true, "peer": true, "dependencies": { @@ -641,6 +1052,48 @@ } } }, + "node_modules/@angular-devkit/build-angular/node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/http-proxy-middleware": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.5.tgz", @@ -689,9 +1142,9 @@ "license": "MIT" }, "node_modules/@angular-devkit/build-angular/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", "engines": { "node": ">=12" @@ -704,7 +1157,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "license": "MIT", "optional": true, "peer": true, "engines": { @@ -755,12 +1207,12 @@ } }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.2003.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2003.12.tgz", - "integrity": "sha512-IkhCU0nAsXYBQOfHu2gQBcYBKhaV1c8wYtu7MmelBcN/iUrG8hRf1sZx+ppUgsdZuBYxCiDiLpcfRVRCIASkvw==", + "version": "0.2003.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2003.25.tgz", + "integrity": "sha512-jJMpYBdWeRfvrCna7JWsyMBbvjMcPblyzh4/pSfWw5znla3hJGzDtmb84qKZ+IB8eZNEcky4iGR5LK2jdGFLAg==", "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.2003.12", + "@angular-devkit/architect": "0.2003.25", "rxjs": "7.8.2" }, "engines": { @@ -774,12 +1226,12 @@ } }, "node_modules/@angular-devkit/schematics": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.3.12.tgz", - "integrity": "sha512-JqJ1u59y+Ud51k/8MHYzSP+aQOeC2PJBaDmMnvqfWVaIt6n3x4gc/VtuhqhpJ0SKulbFuOWgAfI6QbPFrgUYQQ==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.3.25.tgz", + "integrity": "sha512-IB0IHf8ZRqr69hT/XIfHkYLPAYHWQ/WUc6+fKHBwq58jJlm/y5QUeTEuXvJ18IX/+hUIqw+E2Q3T0N9rLDLzXg==", "license": "MIT", "dependencies": { - "@angular-devkit/core": "20.3.12", + "@angular-devkit/core": "20.3.25", "jsonc-parser": "3.3.1", "magic-string": "0.30.17", "ora": "8.2.0", @@ -792,15 +1244,15 @@ } }, "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.12.tgz", - "integrity": "sha512-ReFxd/UOoVDr3+kIUjmYILQZF89qg62POdY7a7OqBH7plmInFlYVSEDouJvGqj3LVCPiqTk2ZOSChbhS/eLxXA==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.25.tgz", + "integrity": "sha512-pSfeWEoS1y9zxqTOw0Etj2NXkRmp/aU52wdnyq3KqVA9cJtgxtlOHlBBNswTil4dReojEy+0K1xJm+mLuWuLxA==", "license": "MIT", "dependencies": { - "ajv": "8.17.1", + "ajv": "8.18.0", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", - "picomatch": "4.0.3", + "picomatch": "4.0.4", "rxjs": "7.8.2", "source-map": "0.7.6" }, @@ -819,9 +1271,9 @@ } }, "node_modules/@angular-devkit/schematics/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -855,7 +1307,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "license": "MIT", "optional": true, "peer": true, "dependencies": { @@ -875,9 +1326,9 @@ "license": "MIT" }, "node_modules/@angular-devkit/schematics/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", "engines": { "node": ">=12" @@ -890,7 +1341,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "license": "MIT", "optional": true, "peer": true, "engines": { @@ -911,9 +1361,10 @@ } }, "node_modules/@angular/animations": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.14.tgz", - "integrity": "sha512-Sx3/XNu2rR+R8T8JkJEaIpZDZPk0IecS0Ayt6HTanNUZXuw0HVou3vkjR5B2St5nM4MXs0gh+S6aLNuArtqJTQ==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.25.tgz", + "integrity": "sha512-lQmti3tI85D525TjUVGqCNLzFxSUoZg+vgIyvuGJZPY0UU/o2S6KAxW6ObmcRotZZHNfenLHIxWgzamBDjIjuw==", + "deprecated": "@angular/animations is deprecated. Use `animate.enter` and `animate.leave` instead. For more information see: https://v22.angular.dev/guide/animations.", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -922,17 +1373,17 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/core": "20.3.14" + "@angular/core": "20.3.25" } }, "node_modules/@angular/build": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-20.3.12.tgz", - "integrity": "sha512-iAZve4VPviC8y6RFctyh3qFXSlP5mth9K46/0zasB4LV4pcmu8BrzIHERxIn/jCDNdVdPh973kxo1ksO4WpyuA==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-20.3.25.tgz", + "integrity": "sha512-ddWmPzYuzDWz9ql0262u9w3OJHXpSjHVNIDFIYOG2XYyMj/Xve0RkJ8/xvV+Hv7T3iqyyGKtsO+n/d0GCJQCtQ==", "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2003.12", + "@angular-devkit/architect": "0.2003.25", "@babel/core": "7.28.3", "@babel/helper-annotate-as-pure": "7.27.3", "@babel/helper-split-export-declaration": "7.24.7", @@ -940,7 +1391,7 @@ "@vitejs/plugin-basic-ssl": "2.1.0", "beasties": "0.3.5", "browserslist": "^4.23.0", - "esbuild": "0.25.9", + "esbuild": "0.28.0", "https-proxy-agent": "7.0.6", "istanbul-lib-instrument": "6.0.3", "jsonc-parser": "3.3.1", @@ -948,14 +1399,14 @@ "magic-string": "0.30.17", "mrmime": "2.0.1", "parse5-html-rewriting-stream": "8.0.0", - "picomatch": "4.0.3", + "picomatch": "4.0.4", "piscina": "5.1.3", - "rollup": "4.52.3", + "rollup": "4.59.0", "sass": "1.90.0", "semver": "7.7.2", "source-map-support": "0.5.21", "tinyglobby": "0.2.14", - "vite": "7.1.11", + "vite": "7.3.2", "watchpack": "2.4.4" }, "engines": { @@ -974,7 +1425,7 @@ "@angular/platform-browser": "^20.0.0", "@angular/platform-server": "^20.0.0", "@angular/service-worker": "^20.0.0", - "@angular/ssr": "^20.3.12", + "@angular/ssr": "^20.3.25", "karma": "^6.4.0", "less": "^4.2.0", "ng-packagr": "^20.0.0", @@ -1023,19 +1474,420 @@ } } }, - "node_modules/@angular/build/node_modules/ansi-escapes": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", - "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", + "node_modules/@angular/build/node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], "license": "MIT", - "dependencies": { - "environment": "^1.0.0" - }, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular/build/node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@angular/build/node_modules/ansi-regex": { @@ -1062,21 +1914,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@angular/build/node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@angular/build/node_modules/cli-truncate": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", @@ -1099,10 +1936,51 @@ "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "license": "MIT" }, + "node_modules/@angular/build/node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, "node_modules/@angular/build/node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "license": "MIT" }, "node_modules/@angular/build/node_modules/is-fullwidth-code-point": { @@ -1134,75 +2012,10 @@ "node": ">=20.0.0" } }, - "node_modules/@angular/build/node_modules/log-update": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", - "license": "MIT", - "dependencies": { - "ansi-escapes": "^7.0.0", - "cli-cursor": "^5.0.0", - "slice-ansi": "^7.1.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular/build/node_modules/log-update/node_modules/is-fullwidth-code-point": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular/build/node_modules/log-update/node_modules/slice-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/@angular/build/node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@angular/build/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", "engines": { "node": ">=12" @@ -1211,22 +2024,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/@angular/build/node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@angular/build/node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -1239,18 +2036,6 @@ "node": ">=10" } }, - "node_modules/@angular/build/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@angular/build/node_modules/slice-ansi": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", @@ -1285,12 +2070,12 @@ } }, "node_modules/@angular/build/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -1317,29 +2102,29 @@ } }, "node_modules/@angular/cli": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.3.12.tgz", - "integrity": "sha512-vqVyVjbFPCRMjA5evL7tV2JeR6Anuzb9WcXTMB17fr7uzKNNAvo7KyRaOJjp+TU4JDARTNyGPy0aywfPx7R60A==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.3.25.tgz", + "integrity": "sha512-QOSxza45CZY11kqPVpqsU+WGYF99rR/r9A9GykZQWuAHb5SEAxlXHyaaGMu/BMtBHy5HNIlJH25UlzelrbaNyQ==", "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.2003.12", - "@angular-devkit/core": "20.3.12", - "@angular-devkit/schematics": "20.3.12", + "@angular-devkit/architect": "0.2003.25", + "@angular-devkit/core": "20.3.25", + "@angular-devkit/schematics": "20.3.25", "@inquirer/prompts": "7.8.2", "@listr2/prompt-adapter-inquirer": "3.0.1", - "@modelcontextprotocol/sdk": "1.17.3", - "@schematics/angular": "20.3.12", + "@modelcontextprotocol/sdk": "1.26.0", + "@schematics/angular": "20.3.25", "@yarnpkg/lockfile": "1.1.0", "algoliasearch": "5.35.0", "ini": "5.0.0", "jsonc-parser": "3.3.1", "listr2": "9.0.1", "npm-package-arg": "13.0.0", - "pacote": "21.0.0", + "pacote": "21.0.4", "resolve": "1.22.10", "semver": "7.7.2", "yargs": "18.0.0", - "zod": "3.25.76" + "zod": "4.1.13" }, "bin": { "ng": "bin/ng.js" @@ -1351,15 +2136,15 @@ } }, "node_modules/@angular/cli/node_modules/@angular-devkit/core": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.12.tgz", - "integrity": "sha512-ReFxd/UOoVDr3+kIUjmYILQZF89qg62POdY7a7OqBH7plmInFlYVSEDouJvGqj3LVCPiqTk2ZOSChbhS/eLxXA==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.25.tgz", + "integrity": "sha512-pSfeWEoS1y9zxqTOw0Etj2NXkRmp/aU52wdnyq3KqVA9cJtgxtlOHlBBNswTil4dReojEy+0K1xJm+mLuWuLxA==", "license": "MIT", "dependencies": { - "ajv": "8.17.1", + "ajv": "8.18.0", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", - "picomatch": "4.0.3", + "picomatch": "4.0.4", "rxjs": "7.8.2", "source-map": "0.7.6" }, @@ -1394,9 +2179,9 @@ } }, "node_modules/@angular/cli/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -1426,21 +2211,6 @@ } } }, - "node_modules/@angular/cli/node_modules/ansi-escapes": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", - "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", - "license": "MIT", - "dependencies": { - "environment": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@angular/cli/node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", @@ -1469,7 +2239,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "license": "MIT", "optional": true, "peer": true, "dependencies": { @@ -1482,21 +2251,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@angular/cli/node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@angular/cli/node_modules/cli-truncate": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", @@ -1513,20 +2267,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@angular/cli/node_modules/cliui": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", - "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", - "license": "ISC", - "dependencies": { - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=20" - } - }, "node_modules/@angular/cli/node_modules/emoji-regex": { "version": "10.6.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", @@ -1534,9 +2274,9 @@ "license": "MIT" }, "node_modules/@angular/cli/node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "license": "MIT" }, "node_modules/@angular/cli/node_modules/is-fullwidth-code-point": { @@ -1574,75 +2314,10 @@ "node": ">=20.0.0" } }, - "node_modules/@angular/cli/node_modules/log-update": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", - "license": "MIT", - "dependencies": { - "ansi-escapes": "^7.0.0", - "cli-cursor": "^5.0.0", - "slice-ansi": "^7.1.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular/cli/node_modules/log-update/node_modules/is-fullwidth-code-point": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular/cli/node_modules/log-update/node_modules/slice-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/@angular/cli/node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@angular/cli/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", "engines": { "node": ">=12" @@ -1655,7 +2330,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "license": "MIT", "optional": true, "peer": true, "engines": { @@ -1666,22 +2340,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@angular/cli/node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@angular/cli/node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -1694,18 +2352,6 @@ "node": ">=10" } }, - "node_modules/@angular/cli/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@angular/cli/node_modules/slice-ansi": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", @@ -1749,12 +2395,12 @@ } }, "node_modules/@angular/cli/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -1780,45 +2426,10 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@angular/cli/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/@angular/cli/node_modules/yargs": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", - "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", - "license": "MIT", - "dependencies": { - "cliui": "^9.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "string-width": "^7.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^22.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=23" - } - }, - "node_modules/@angular/cli/node_modules/yargs-parser": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", - "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", - "license": "ISC", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=23" - } - }, "node_modules/@angular/common": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.14.tgz", - "integrity": "sha512-OOUvjTtnpktJLsNupA+GFT2q5zNocPdpOENA8aSrXvAheNybLjgi+otO3U3sQsvB1VwaoEZ9GT5O3lZlstnA/A==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.25.tgz", + "integrity": "sha512-rnRGcXbjet0DHgkRL4Dqxk21G2T4UypVfiTV/fay58H8w9U89PJ1L6gRmk8B/uyfpii/9r23cBwnpcguQykxYw==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1827,14 +2438,14 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/core": "20.3.14", + "@angular/core": "20.3.25", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.14.tgz", - "integrity": "sha512-KFbfPPAbclzGDujCVruflCD9j4Zwwxvrg7Y4C9GJYs3LZ85t+BfIMDDnvpBUM07ZLnfY4TO4gQdHmJAcaGGXDQ==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.25.tgz", + "integrity": "sha512-TSh6gVoQqlLPqWwsYMK0lfVEQYENQO+USzS+BHFXEHFfgBRap6qDpIUGnRdj0Y2PlaVJUVFbeq1855EZUPUEoA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1844,9 +2455,9 @@ } }, "node_modules/@angular/compiler-cli": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.14.tgz", - "integrity": "sha512-lFg9ikwRClzDPjdFiwynbVFIi1RJZf/0i+OHa3Ns2gzXxJeHNKMJrHHjWZ2DU4N2UpxH0YAPe22N9Bie28IuQQ==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.25.tgz", + "integrity": "sha512-iqxwVo5Pgzt3EfT49OZ6plxA6KKxwv7ixx1XNH7QRvaOJC9gmsPScWpx+LO7ZsVZdo/NkA+rnXDl0PauUgGciw==", "license": "MIT", "dependencies": { "@babel/core": "7.28.3", @@ -1866,7 +2477,7 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "20.3.14", + "@angular/compiler": "20.3.25", "typescript": ">=5.8 <6.0" }, "peerDependenciesMeta": { @@ -1875,30 +2486,6 @@ } } }, - "node_modules/@angular/compiler-cli/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@angular/compiler-cli/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/@angular/compiler-cli/node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -1914,26 +2501,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@angular/compiler-cli/node_modules/cliui": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", - "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", - "license": "ISC", - "dependencies": { - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/@angular/compiler-cli/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "license": "MIT" - }, "node_modules/@angular/compiler-cli/node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -1947,94 +2514,10 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@angular/compiler-cli/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular/compiler-cli/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@angular/compiler-cli/node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@angular/compiler-cli/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/@angular/compiler-cli/node_modules/yargs": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", - "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", - "license": "MIT", - "dependencies": { - "cliui": "^9.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "string-width": "^7.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^22.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=23" - } - }, - "node_modules/@angular/compiler-cli/node_modules/yargs-parser": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", - "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", - "license": "ISC", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=23" - } - }, "node_modules/@angular/core": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.14.tgz", - "integrity": "sha512-rpyEbhWF6Fj/xI9IvNLZh5QBUYnoXuF7vX54CCtyQ2MHALxRR/aa1WRxjRM96cF2OqodQ/Gj3oYW8ei8hlBh4w==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.25.tgz", + "integrity": "sha512-B4XnnR5jzikZDvZ4PjwjAWZMT14dxrKrmJdwa/n0yp7rMPkIJTKF6ZJMg4d1pLWLLSsc2oWHioN3UrWlGqIKnA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -2043,7 +2526,7 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "20.3.14", + "@angular/compiler": "20.3.25", "rxjs": "^6.5.3 || ^7.4.0", "zone.js": "~0.15.0" }, @@ -2057,9 +2540,9 @@ } }, "node_modules/@angular/forms": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.14.tgz", - "integrity": "sha512-fGrJ589tU+AKoxf+kaRrEw7wlSfVr1/z/Fz625ggFCc6ySQEityKW3JsnLfNkh5qGrdxib4BOfF78f9J7Pyk+w==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.25.tgz", + "integrity": "sha512-vGRo1LVPFo2Cu0k+QyDTlsBv5UbN0c3Et2YMS+43oyi1c4keocntBccOjLyM5C0kpMz4+pP81MqqYpAWu2k+TQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -2068,16 +2551,16 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.3.14", - "@angular/core": "20.3.14", - "@angular/platform-browser": "20.3.14", + "@angular/common": "20.3.25", + "@angular/core": "20.3.25", + "@angular/platform-browser": "20.3.25", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/language-service": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-20.3.14.tgz", - "integrity": "sha512-3Jvi60WzLUe6jQJEw1xi/35uW7ynzxOS7iyZlwYfl2v8RljeLyyQsm0WNVpq6tXt80ppDeD59JvYTguEQ283Og==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-20.3.25.tgz", + "integrity": "sha512-3PZUwbDUVQXk9BLSiNUpDxTnRXGR3szwK9QFQaGDt8lhmLYaQLh3VJsY0FHDRghHZYIGR2P2MbVxGQHytF+IPw==", "dev": true, "license": "MIT", "engines": { @@ -2085,9 +2568,9 @@ } }, "node_modules/@angular/localize": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-20.3.14.tgz", - "integrity": "sha512-tSYZmFhjCHwifWE+R1VHg3zaemUX2tNjpQd9Ha6BvISyzyGWw/sQirIAxC4uoa2RVZ3jexos5sbw8rFT6iIEYg==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-20.3.25.tgz", + "integrity": "sha512-N4wmEBH44h58Av+1ivQ7LAe6Q0QP/2oBngvmnkO39AM6tadlnQGmaOvVzCUZe4BpOWYrrXQNiszSxGD3mUGgHg==", "license": "MIT", "dependencies": { "@babel/core": "7.28.3", @@ -2104,142 +2587,14 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "20.3.14", - "@angular/compiler-cli": "20.3.14" - } - }, - "node_modules/@angular/localize/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@angular/localize/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@angular/localize/node_modules/cliui": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", - "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", - "license": "ISC", - "dependencies": { - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/@angular/localize/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "license": "MIT" - }, - "node_modules/@angular/localize/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular/localize/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@angular/localize/node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@angular/localize/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/@angular/localize/node_modules/yargs": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", - "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", - "license": "MIT", - "dependencies": { - "cliui": "^9.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "string-width": "^7.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^22.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=23" - } - }, - "node_modules/@angular/localize/node_modules/yargs-parser": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", - "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", - "license": "ISC", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=23" + "@angular/compiler": "20.3.25", + "@angular/compiler-cli": "20.3.25" } }, "node_modules/@angular/platform-browser": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.14.tgz", - "integrity": "sha512-Lviz9GfsIyOIBDal8QhIBKU8OMH29A0RhFw2opTC50sqKadXLN9CD7iSaAwQbNLc4mc3JAF4zth0AzKdHLbz7Q==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.25.tgz", + "integrity": "sha512-0k06U/AJRQifGMLkcU3R9uEHWbuKEzkKMuKcGagXTrkeFvCG2Ub4JdsbcjFNWB2bspWgaxIMSceuj7c83U5wOA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -2248,9 +2603,9 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/animations": "20.3.14", - "@angular/common": "20.3.14", - "@angular/core": "20.3.14" + "@angular/animations": "20.3.25", + "@angular/common": "20.3.25", + "@angular/core": "20.3.25" }, "peerDependenciesMeta": { "@angular/animations": { @@ -2259,9 +2614,9 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.14.tgz", - "integrity": "sha512-g9z/g8gIOrBCX1SQ/GWwB0+JXBC6CKe0+yRyy9GGeBLm/YXWZHxTkmnDmueXXfPtUl8TOAInE22wlLcfunWTrg==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.25.tgz", + "integrity": "sha512-3Ku+IsN4tQPVBsw75SoLbLf7TsXAGL0rGPHSsyNYFhG2ZZeQuYNIAi8mc4cwz/qMDnuassHFrCxuLDgN6Yab5w==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -2270,16 +2625,16 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.3.14", - "@angular/compiler": "20.3.14", - "@angular/core": "20.3.14", - "@angular/platform-browser": "20.3.14" + "@angular/common": "20.3.25", + "@angular/compiler": "20.3.25", + "@angular/core": "20.3.25", + "@angular/platform-browser": "20.3.25" } }, "node_modules/@angular/platform-server": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-20.3.14.tgz", - "integrity": "sha512-CTc/K3AdOKjtU3PzK5cH8aRjpUZ2p7PVZ3JaVf9KMUHdRwNkPjMuRugoU4Adm5S3lGW4PsDuP49I6GkMsVDN6w==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-20.3.25.tgz", + "integrity": "sha512-uOpLILe5QP9WLXhwshA3fbHc+Wx/4nrUBZNns/dw3t06bRHMbbkutF8eVs+n6Atl332y4mOcStzMX1ilBUSFHw==", "license": "MIT", "dependencies": { "tslib": "^2.3.0", @@ -2289,17 +2644,17 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.3.14", - "@angular/compiler": "20.3.14", - "@angular/core": "20.3.14", - "@angular/platform-browser": "20.3.14", + "@angular/common": "20.3.25", + "@angular/compiler": "20.3.25", + "@angular/core": "20.3.25", + "@angular/platform-browser": "20.3.25", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/router": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.14.tgz", - "integrity": "sha512-gi7/NuHRS9n9RCwh03VuVFizVMa2lKL/s+7yP3Ecq2nQ5uSeTMWb/91OmGEBwncI3wKPkYdQ9g3n6PvK/O8uDQ==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.25.tgz", + "integrity": "sha512-YIjLHWAufTaukNj15hEoys29e7XNhnCRsS1/95h/OqR69R3adbB8hV7ut7gO6XdXokriYqb4gtoUjoESxR+xFQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -2308,16 +2663,16 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.3.14", - "@angular/core": "20.3.14", - "@angular/platform-browser": "20.3.14", + "@angular/common": "20.3.25", + "@angular/core": "20.3.25", + "@angular/platform-browser": "20.3.25", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/ssr": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular/ssr/-/ssr-20.3.12.tgz", - "integrity": "sha512-liIxrlozOkcx+6qkMb0rFcKjo32aRtUI/U7TQ+1KA1XZrIk97aTjHEpq0KhBMjNlOt/xwrlUARDXjS5au5kP5g==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/ssr/-/ssr-20.3.25.tgz", + "integrity": "sha512-A/lbXQ+GucLAQfCJ5img7/xuqlftWjJU+iJABq/ARDkOOE/rNHrxFkXjJBntj97+oXjr7HBYI2sxYRZCCEiEag==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -2335,12 +2690,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -2596,27 +2951,27 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -2638,9 +2993,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -2697,7 +3052,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", - "license": "MIT", "dependencies": { "@babel/types": "^7.24.7" }, @@ -2760,12 +3114,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -3308,15 +3662,15 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", - "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", + "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.5" + "@babel/traverse": "^7.29.0" }, "engines": { "node": ">=6.9.0" @@ -3862,31 +4216,31 @@ } }, "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -3894,13 +4248,13 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -3920,9 +4274,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -3932,16 +4286,6 @@ "node": ">=6.9.0" } }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -3955,10 +4299,9 @@ } }, "node_modules/@cypress/request": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.9.tgz", - "integrity": "sha512-I3l7FdGRXluAS44/0NguwWlO83J18p0vlr2FYHrJkWdNYhgVoiYo61IXPqaOsL+vNxU1ZqMACzItGK3/KKDsdw==", - "license": "Apache-2.0", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-4.0.1.tgz", + "integrity": "sha512-y20e+e6dFYkOUUJLVUZTsJRuTiXZaUQ32WD+R/ux/HBybbTx4ge7cNINcua0pU8+SNkKuRbOF12mBmzuzM8n5w==", "optional": true, "dependencies": { "aws-sign2": "~0.7.0", @@ -3974,21 +4317,19 @@ "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", - "qs": "6.14.0", + "qs": "^6.15.2", "safe-buffer": "^5.1.2", "tough-cookie": "^5.0.0", - "tunnel-agent": "^0.6.0", - "uuid": "^8.3.2" + "tunnel-agent": "^0.6.0" }, "engines": { - "node": ">= 6" + "node": ">= 14.17.0" } }, "node_modules/@cypress/request/node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "license": "BSD-3-Clause", + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", "optional": true, "dependencies": { "side-channel": "^1.1.0" @@ -4061,9 +4402,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", - "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", "cpu": [ "ppc64" ], @@ -4077,9 +4418,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", - "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", "cpu": [ "arm" ], @@ -4093,9 +4434,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", - "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", "cpu": [ "arm64" ], @@ -4109,9 +4450,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", - "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", "cpu": [ "x64" ], @@ -4125,9 +4466,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", - "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", "cpu": [ "arm64" ], @@ -4141,9 +4482,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", - "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", "cpu": [ "x64" ], @@ -4157,9 +4498,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", - "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", "cpu": [ "arm64" ], @@ -4173,9 +4514,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", - "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", "cpu": [ "x64" ], @@ -4189,9 +4530,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", - "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", "cpu": [ "arm" ], @@ -4205,9 +4546,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", - "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", "cpu": [ "arm64" ], @@ -4221,9 +4562,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", - "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", "cpu": [ "ia32" ], @@ -4237,9 +4578,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", - "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", "cpu": [ "loong64" ], @@ -4253,9 +4594,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", - "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", "cpu": [ "mips64el" ], @@ -4269,9 +4610,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", - "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", "cpu": [ "ppc64" ], @@ -4285,9 +4626,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", - "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", "cpu": [ "riscv64" ], @@ -4301,9 +4642,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", - "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", "cpu": [ "s390x" ], @@ -4317,9 +4658,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", - "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", "cpu": [ "x64" ], @@ -4333,9 +4674,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", - "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", "cpu": [ "arm64" ], @@ -4349,9 +4690,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", - "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", "cpu": [ "x64" ], @@ -4365,9 +4706,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", - "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", "cpu": [ "arm64" ], @@ -4381,9 +4722,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", - "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", "cpu": [ "x64" ], @@ -4397,9 +4738,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", - "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", "cpu": [ "arm64" ], @@ -4413,9 +4754,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", - "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", "cpu": [ "x64" ], @@ -4429,9 +4770,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", - "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", "cpu": [ "arm64" ], @@ -4445,9 +4786,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", - "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", "cpu": [ "ia32" ], @@ -4461,9 +4802,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", - "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", "cpu": [ "x64" ], @@ -4738,6 +5079,18 @@ "@hapi/hoek": "^11.0.2" } }, + "node_modules/@hono/node-server": { + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -5152,128 +5505,10 @@ } } }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "license": "ISC", - "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" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", "dependencies": { "minipass": "^7.0.4" }, @@ -5285,7 +5520,6 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "license": "MIT", "engines": { "node": ">=8" } @@ -5345,7 +5579,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", - "license": "Apache-2.0", "engines": { "node": ">=10.0" }, @@ -5358,10 +5591,9 @@ } }, "node_modules/@jsonjoy.com/buffers": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", - "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", - "license": "Apache-2.0", + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz", + "integrity": "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==", "engines": { "node": ">=10.0" }, @@ -5377,7 +5609,256 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", - "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-core": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.56.10.tgz", + "integrity": "sha512-PyAEA/3cnHhsGcdY+AmIU+ZPqTuZkDhCXQ2wkXypdLitSpd6d5Ivxhnq4wa2ETRWFVJGabYynBWxIijOswSmOw==", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.56.10", + "@jsonjoy.com/fs-node-utils": "4.56.10", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-fsa": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.56.10.tgz", + "integrity": "sha512-/FVK63ysNzTPOnCCcPoPHt77TOmachdMS422txM4KhxddLdbW1fIbFMYH0AM0ow/YchCyS5gqEjKLNyv71j/5Q==", + "dependencies": { + "@jsonjoy.com/fs-core": "4.56.10", + "@jsonjoy.com/fs-node-builtins": "4.56.10", + "@jsonjoy.com/fs-node-utils": "4.56.10", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.56.10.tgz", + "integrity": "sha512-7R4Gv3tkUdW3dXfXiOkqxkElxKNVdd8BDOWC0/dbERd0pXpPY+s2s1Mino+aTvkGrFPiY+mmVxA7zhskm4Ue4Q==", + "dependencies": { + "@jsonjoy.com/fs-core": "4.56.10", + "@jsonjoy.com/fs-node-builtins": "4.56.10", + "@jsonjoy.com/fs-node-utils": "4.56.10", + "@jsonjoy.com/fs-print": "4.56.10", + "@jsonjoy.com/fs-snapshot": "4.56.10", + "glob-to-regex.js": "^1.0.0", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-builtins": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.56.10.tgz", + "integrity": "sha512-uUnKz8R0YJyKq5jXpZtkGV9U0pJDt8hmYcLRrPjROheIfjMXsz82kXMgAA/qNg0wrZ1Kv+hrg7azqEZx6XZCVw==", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-to-fsa": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.56.10.tgz", + "integrity": "sha512-oH+O6Y4lhn9NyG6aEoFwIBNKZeYy66toP5LJcDOMBgL99BKQMUf/zWJspdRhMdn/3hbzQsZ8EHHsuekbFLGUWw==", + "dependencies": { + "@jsonjoy.com/fs-fsa": "4.56.10", + "@jsonjoy.com/fs-node-builtins": "4.56.10", + "@jsonjoy.com/fs-node-utils": "4.56.10" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-utils": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.56.10.tgz", + "integrity": "sha512-8EuPBgVI2aDPwFdaNQeNpHsyqPi3rr+85tMNG/lHvQLiVjzoZsvxA//Xd8aB567LUhy4QS03ptT+unkD/DIsNg==", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.56.10" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-print": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.56.10.tgz", + "integrity": "sha512-JW4fp5mAYepzFsSGrQ48ep8FXxpg4niFWHdF78wDrFGof7F3tKDJln72QFDEn/27M1yHd4v7sKHHVPh78aWcEw==", + "dependencies": { + "@jsonjoy.com/fs-node-utils": "4.56.10", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.56.10.tgz", + "integrity": "sha512-DkR6l5fj7+qj0+fVKm/OOXMGfDFCGXLfyHkORH3DF8hxkpDgIHbhf/DwncBMs2igu/ST7OEkexn1gIqoU6Y+9g==", + "dependencies": { + "@jsonjoy.com/buffers": "^17.65.0", + "@jsonjoy.com/fs-node-utils": "4.56.10", + "@jsonjoy.com/json-pack": "^17.65.0", + "@jsonjoy.com/util": "^17.65.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/base64": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.67.0.tgz", + "integrity": "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/codegen": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz", + "integrity": "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pack": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz", + "integrity": "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==", + "dependencies": { + "@jsonjoy.com/base64": "17.67.0", + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0", + "@jsonjoy.com/json-pointer": "17.67.0", + "@jsonjoy.com/util": "17.67.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pointer": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz", + "integrity": "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==", + "dependencies": { + "@jsonjoy.com/util": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/util": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.67.0.tgz", + "integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==", + "dependencies": { + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0" + }, "engines": { "node": ">=10.0" }, @@ -5393,7 +5874,6 @@ "version": "1.21.0", "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", - "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/base64": "^1.1.2", "@jsonjoy.com/buffers": "^1.2.0", @@ -5415,11 +5895,25 @@ "tslib": "2" } }, + "node_modules/@jsonjoy.com/json-pack/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/@jsonjoy.com/json-pointer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", - "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/codegen": "^1.0.0", "@jsonjoy.com/util": "^1.9.0" @@ -5439,7 +5933,6 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", - "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/buffers": "^1.0.0", "@jsonjoy.com/codegen": "^1.0.0" @@ -5455,11 +5948,25 @@ "tslib": "2" } }, + "node_modules/@jsonjoy.com/util/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", - "license": "MIT" + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==" }, "node_modules/@lmdb/lmdb-darwin-arm64": { "version": "3.4.2", @@ -5468,7 +5975,6 @@ "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "darwin" @@ -5481,7 +5987,6 @@ "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "darwin" @@ -5494,7 +5999,6 @@ "cpu": [ "arm" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -5507,7 +6011,6 @@ "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -5520,7 +6023,6 @@ "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -5533,7 +6035,6 @@ "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "win32" @@ -5546,39 +6047,108 @@ "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.17.3", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.3.tgz", - "integrity": "sha512-JPwUKWSsbzx+DLFznf/QZ32Qa+ptfbUlHhRLrBQBAFu9iI1iYvizM4p+zhhRDceSsPutXp4z+R/HPVphlIiclg==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", + "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", "license": "MIT", "dependencies": { - "ajv": "^6.12.6", + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -5591,21 +6161,36 @@ "url": "https://opencollective.com/express" } }, + "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/@modelcontextprotocol/sdk/node_modules/raw-body": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", - "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.7.0", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.10" } }, + "node_modules/@modelcontextprotocol/sdk/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", @@ -5613,7 +6198,6 @@ "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "darwin" @@ -5626,7 +6210,6 @@ "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "darwin" @@ -5639,7 +6222,6 @@ "cpu": [ "arm" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -5652,7 +6234,6 @@ "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -5665,7 +6246,6 @@ "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -5678,7 +6258,6 @@ "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "win32" @@ -5688,7 +6267,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.1.1.tgz", "integrity": "sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==", - "license": "MIT", "optional": true, "engines": { "node": ">= 10" @@ -5724,7 +6302,6 @@ "cpu": [ "arm" ], - "license": "MIT", "optional": true, "os": [ "android" @@ -5740,7 +6317,6 @@ "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "android" @@ -5756,7 +6332,6 @@ "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "darwin" @@ -5772,7 +6347,6 @@ "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "darwin" @@ -5788,7 +6362,6 @@ "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "freebsd" @@ -5804,7 +6377,6 @@ "cpu": [ "arm" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -5820,7 +6392,6 @@ "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -5836,7 +6407,6 @@ "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -5852,7 +6422,6 @@ "cpu": [ "ppc64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -5868,7 +6437,6 @@ "cpu": [ "riscv64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -5884,7 +6452,6 @@ "cpu": [ "s390x" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -5900,7 +6467,6 @@ "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -5916,7 +6482,6 @@ "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -5932,7 +6497,6 @@ "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "openharmony" @@ -5948,7 +6512,6 @@ "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "win32" @@ -5964,7 +6527,6 @@ "cpu": [ "ia32" ], - "license": "MIT", "optional": true, "os": [ "win32" @@ -5980,7 +6542,6 @@ "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "win32" @@ -6007,9 +6568,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-20.3.12.tgz", - "integrity": "sha512-ePuofHOtbgvEq2t+hcmL30s4q9HQ/nv9ABwpLiELdVIObcWUnrnizAvM7hujve/9CQL6gRCeEkxPLPS4ZrK9AQ==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-20.3.25.tgz", + "integrity": "sha512-p/YopAgukaIvezv3hsJzJevnNUBNTM8UQIkyDHPY5/aANdKraUTKDQUwlVxRiyD8U+PTSeD/BI56oOnuKrdFZQ==", "license": "MIT", "engines": { "node": "^20.19.0 || ^22.12.0 || >=24.0.0", @@ -6022,6 +6583,14 @@ "webpack": "^5.54.0" } }, + "node_modules/@noble/secp256k1": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-3.1.0.tgz", + "integrity": "sha512-+F7iS7tUMaNGXcc9X3PjmjvuQnXEuSjCRNzVVA2xAcKXgCaP0dHYz4SFyt4FKNHef7sOP//xihowcySSS7PK9g==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -6055,78 +6624,77 @@ } }, "node_modules/@npmcli/agent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", - "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", - "license": "ISC", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", + "integrity": "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==", "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", - "lru-cache": "^10.0.1", + "lru-cache": "^11.2.1", "socks-proxy-agent": "^8.0.3" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@npmcli/agent/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, "node_modules/@npmcli/fs": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", - "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", - "license": "ISC", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-5.0.0.tgz", + "integrity": "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==", "dependencies": { "semver": "^7.3.5" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/git": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-6.0.3.tgz", - "integrity": "sha512-GUYESQlxZRAdhs3UhbB6pVRNUELQOHXwK9ruDkwmCv2aZ5y0SApQzUJCg02p3A7Ue2J5hxvlk1YI53c00NmRyQ==", - "license": "ISC", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-7.0.1.tgz", + "integrity": "sha512-+XTFxK2jJF/EJJ5SoAzXk3qwIDfvFc5/g+bD274LZ7uY7LE8sTfG6Z8rOanPl2ZEvZWqNvmEdtXC25cE54VcoA==", "dependencies": { - "@npmcli/promise-spawn": "^8.0.0", - "ini": "^5.0.0", - "lru-cache": "^10.0.1", - "npm-pick-manifest": "^10.0.0", - "proc-log": "^5.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "ini": "^6.0.0", + "lru-cache": "^11.2.1", + "npm-pick-manifest": "^11.0.1", + "proc-log": "^6.0.0", "promise-retry": "^2.0.1", "semver": "^7.3.5", - "which": "^5.0.0" + "which": "^6.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/git/node_modules/ini": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/git/node_modules/isexe": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "license": "ISC", "engines": { "node": ">=16" } }, - "node_modules/@npmcli/git/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" + "node_modules/@npmcli/git/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } }, "node_modules/@npmcli/git/node_modules/which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", - "license": "ISC", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.0.tgz", + "integrity": "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==", "dependencies": { "isexe": "^3.1.1" }, @@ -6134,105 +6702,88 @@ "node-which": "bin/which.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/installed-package-contents": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-3.0.0.tgz", - "integrity": "sha512-fkxoPuFGvxyrH+OQzyTkX2LUEamrF4jZSmxjAtPPHHGO0dqsQ8tTKjnIS8SAnPHdk2I03BDtSMR5K/4loKg79Q==", - "license": "ISC", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-4.0.0.tgz", + "integrity": "sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA==", "dependencies": { - "npm-bundled": "^4.0.0", - "npm-normalize-package-bin": "^4.0.0" + "npm-bundled": "^5.0.0", + "npm-normalize-package-bin": "^5.0.0" }, "bin": { "installed-package-contents": "bin/index.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/node-gyp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-4.0.0.tgz", - "integrity": "sha512-+t5DZ6mO/QFh78PByMq1fGSAub/agLJZDRfJRMeOSNCt8s9YVlTjmGpIPwPhvXTGUIJk+WszlT0rQa1W33yzNA==", - "license": "ISC", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-5.0.0.tgz", + "integrity": "sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ==", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/package-json": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-6.2.0.tgz", - "integrity": "sha512-rCNLSB/JzNvot0SEyXqWZ7tX2B5dD2a1br2Dp0vSYVo5jh8Z0EZ7lS9TsZ1UtziddB1UfNUaMCc538/HztnJGA==", - "license": "ISC", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-7.0.4.tgz", + "integrity": "sha512-0wInJG3j/K40OJt/33ax47WfWMzZTm6OQxB9cDhTt5huCP2a9g2GnlsxmfN+PulItNPIpPrZ+kfwwUil7eHcZQ==", "dependencies": { - "@npmcli/git": "^6.0.0", - "glob": "^10.2.2", - "hosted-git-info": "^8.0.0", - "json-parse-even-better-errors": "^4.0.0", - "proc-log": "^5.0.0", + "@npmcli/git": "^7.0.0", + "glob": "^13.0.0", + "hosted-git-info": "^9.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", "semver": "^7.5.3", "validate-npm-package-license": "^3.0.4" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@npmcli/package-json/node_modules/hosted-git-info": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", - "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", - "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/package-json/node_modules/json-parse-even-better-errors": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", - "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", - "license": "MIT", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-5.0.0.tgz", + "integrity": "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ==", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@npmcli/package-json/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" + "node_modules/@npmcli/package-json/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } }, "node_modules/@npmcli/promise-spawn": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-8.0.3.tgz", - "integrity": "sha512-Yb00SWaL4F8w+K8YGhQ55+xE4RUNdMHV43WZGsiTM92gS+lC0mGsn7I4hLug7pbao035S6bj3Y3w0cUNGLfmkg==", - "license": "ISC", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-9.0.1.tgz", + "integrity": "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==", "dependencies": { - "which": "^5.0.0" + "which": "^6.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/promise-spawn/node_modules/isexe": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "license": "ISC", "engines": { "node": ">=16" } }, "node_modules/@npmcli/promise-spawn/node_modules/which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", - "license": "ISC", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.0.tgz", + "integrity": "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==", "dependencies": { "isexe": "^3.1.1" }, @@ -6240,49 +6791,53 @@ "node-which": "bin/which.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/redact": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-3.2.2.tgz", - "integrity": "sha512-7VmYAmk4csGv08QzrDKScdzn11jHPFGyqJW39FyPgPuAp3zIaUmuCo1yxw9aGs+NEJuTGQ9Gwqpt93vtJubucg==", - "license": "ISC", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-4.0.0.tgz", + "integrity": "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/run-script": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-9.1.0.tgz", - "integrity": "sha512-aoNSbxtkePXUlbZB+anS1LqsJdctG5n3UVhfU47+CDdwMi6uNTBMF9gPcQRnqghQd2FGzcwwIFBruFMxjhBewg==", - "license": "ISC", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-10.0.3.tgz", + "integrity": "sha512-ER2N6itRkzWbbtVmZ9WKaWxVlKlOeBFF1/7xx+KA5J1xKa4JjUwBdb6tDpk0v1qA+d+VDwHI9qmLcXSWcmi+Rw==", "dependencies": { - "@npmcli/node-gyp": "^4.0.0", - "@npmcli/package-json": "^6.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "node-gyp": "^11.0.0", - "proc-log": "^5.0.0", - "which": "^5.0.0" + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "node-gyp": "^12.1.0", + "proc-log": "^6.0.0", + "which": "^6.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/run-script/node_modules/isexe": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "license": "ISC", "engines": { "node": ">=16" } }, + "node_modules/@npmcli/run-script/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/@npmcli/run-script/node_modules/which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", - "license": "ISC", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.0.tgz", + "integrity": "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==", "dependencies": { "isexe": "^3.1.1" }, @@ -6290,7 +6845,7 @@ "node-which": "bin/which.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@parcel/watcher": { @@ -6609,16 +7164,6 @@ "license": "MIT", "optional": true }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -6630,9 +7175,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.3.tgz", - "integrity": "sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", "cpu": [ "arm" ], @@ -6643,9 +7188,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.3.tgz", - "integrity": "sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", "cpu": [ "arm64" ], @@ -6656,9 +7201,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.3.tgz", - "integrity": "sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", "cpu": [ "arm64" ], @@ -6669,9 +7214,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.3.tgz", - "integrity": "sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", "cpu": [ "x64" ], @@ -6682,9 +7227,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.3.tgz", - "integrity": "sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", "cpu": [ "arm64" ], @@ -6695,9 +7240,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.3.tgz", - "integrity": "sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", "cpu": [ "x64" ], @@ -6708,9 +7253,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.3.tgz", - "integrity": "sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", "cpu": [ "arm" ], @@ -6721,9 +7266,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.3.tgz", - "integrity": "sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", "cpu": [ "arm" ], @@ -6734,9 +7279,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.3.tgz", - "integrity": "sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", "cpu": [ "arm64" ], @@ -6747,9 +7292,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.3.tgz", - "integrity": "sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", "cpu": [ "arm64" ], @@ -6760,9 +7305,22 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.3.tgz", - "integrity": "sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", "cpu": [ "loong64" ], @@ -6773,9 +7331,22 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.3.tgz", - "integrity": "sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", "cpu": [ "ppc64" ], @@ -6786,9 +7357,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.3.tgz", - "integrity": "sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", "cpu": [ "riscv64" ], @@ -6799,9 +7370,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.3.tgz", - "integrity": "sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", "cpu": [ "riscv64" ], @@ -6812,9 +7383,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.3.tgz", - "integrity": "sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", "cpu": [ "s390x" ], @@ -6825,9 +7396,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.3.tgz", - "integrity": "sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", "cpu": [ "x64" ], @@ -6838,9 +7409,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.3.tgz", - "integrity": "sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", "cpu": [ "x64" ], @@ -6850,10 +7421,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.3.tgz", - "integrity": "sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", "cpu": [ "arm64" ], @@ -6864,9 +7448,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.3.tgz", - "integrity": "sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", "cpu": [ "arm64" ], @@ -6877,9 +7461,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.3.tgz", - "integrity": "sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", "cpu": [ "ia32" ], @@ -6890,9 +7474,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.3.tgz", - "integrity": "sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", "cpu": [ "x64" ], @@ -6903,9 +7487,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.3.tgz", - "integrity": "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", "cpu": [ "x64" ], @@ -6916,13 +7500,13 @@ ] }, "node_modules/@schematics/angular": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.3.12.tgz", - "integrity": "sha512-ikl+nkWUab/Z4eSkBHgq9FLIUH8qh4OcYKeBQ0fyWqIUFHyjjK0JOfwmH1g/3zAmuUMtkthHCehAtyKzCTQjVA==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.3.25.tgz", + "integrity": "sha512-ezoJpPKhhjXgE3LxuNcJO/ghSXs8f73xrqGvqouag7IbuliJYbHrt22WH3X75XSbD0+iOdiqA2bARvAO/ezyxQ==", "license": "MIT", "dependencies": { - "@angular-devkit/core": "20.3.12", - "@angular-devkit/schematics": "20.3.12", + "@angular-devkit/core": "20.3.25", + "@angular-devkit/schematics": "20.3.25", "jsonc-parser": "3.3.1" }, "engines": { @@ -6932,15 +7516,15 @@ } }, "node_modules/@schematics/angular/node_modules/@angular-devkit/core": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.12.tgz", - "integrity": "sha512-ReFxd/UOoVDr3+kIUjmYILQZF89qg62POdY7a7OqBH7plmInFlYVSEDouJvGqj3LVCPiqTk2ZOSChbhS/eLxXA==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.25.tgz", + "integrity": "sha512-pSfeWEoS1y9zxqTOw0Etj2NXkRmp/aU52wdnyq3KqVA9cJtgxtlOHlBBNswTil4dReojEy+0K1xJm+mLuWuLxA==", "license": "MIT", "dependencies": { - "ajv": "8.17.1", + "ajv": "8.18.0", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", - "picomatch": "4.0.3", + "picomatch": "4.0.4", "rxjs": "7.8.2", "source-map": "0.7.6" }, @@ -6959,9 +7543,9 @@ } }, "node_modules/@schematics/angular/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -6995,7 +7579,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "license": "MIT", "optional": true, "peer": true, "dependencies": { @@ -7015,9 +7598,9 @@ "license": "MIT" }, "node_modules/@schematics/angular/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", "engines": { "node": ">=12" @@ -7030,7 +7613,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "license": "MIT", "optional": true, "peer": true, "engines": { @@ -7051,77 +7633,79 @@ } }, "node_modules/@sigstore/bundle": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-3.1.0.tgz", - "integrity": "sha512-Mm1E3/CmDDCz3nDhFKTuYdB47EdRFRQMOE/EAbiG1MJW77/w1b3P7Qx7JSrVJs8PfwOLOVcKQCHErIwCTyPbag==", - "license": "Apache-2.0", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-4.0.0.tgz", + "integrity": "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==", "dependencies": { - "@sigstore/protobuf-specs": "^0.4.0" + "@sigstore/protobuf-specs": "^0.5.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sigstore/core": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-2.0.0.tgz", - "integrity": "sha512-nYxaSb/MtlSI+JWcwTHQxyNmWeWrUXJJ/G4liLrGG7+tS4vAz6LF3xRXqLH6wPIVUoZQel2Fs4ddLx4NCpiIYg==", - "license": "Apache-2.0", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-3.1.0.tgz", + "integrity": "sha512-o5cw1QYhNQ9IroioJxpzexmPjfCe7gzafd2RY3qnMpxr4ZEja+Jad/U8sgFpaue6bOaF+z7RVkyKVV44FN+N8A==", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sigstore/protobuf-specs": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.4.3.tgz", - "integrity": "sha512-fk2zjD9117RL9BjqEwF7fwv7Q/P9yGsMV4MUJZ/DocaQJ6+3pKr+syBq1owU5Q5qGw5CUbXzm+4yJ2JVRDQeSA==", - "license": "Apache-2.0", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.5.0.tgz", + "integrity": "sha512-MM8XIwUjN2bwvCg1QvrMtbBmpcSHrkhFSCu1D11NyPvDQ25HEc4oG5/OcQfd/Tlf/OxmKWERDj0zGE23jQaMwA==", "engines": { "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@sigstore/sign": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-3.1.0.tgz", - "integrity": "sha512-knzjmaOHOov1Ur7N/z4B1oPqZ0QX5geUfhrVaqVlu+hl0EAoL4o+l0MSULINcD5GCWe3Z0+YJO8ues6vFlW0Yw==", - "license": "Apache-2.0", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-4.1.0.tgz", + "integrity": "sha512-Vx1RmLxLGnSUqx/o5/VsCjkuN5L7y+vxEEwawvc7u+6WtX2W4GNa7b9HEjmcRWohw/d6BpATXmvOwc78m+Swdg==", "dependencies": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.4.0", - "make-fetch-happen": "^14.0.2", - "proc-log": "^5.0.0", + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0", + "make-fetch-happen": "^15.0.3", + "proc-log": "^6.1.0", "promise-retry": "^2.0.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/sign/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sigstore/tuf": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-3.1.1.tgz", - "integrity": "sha512-eFFvlcBIoGwVkkwmTi/vEQFSva3xs5Ot3WmBcjgjVdiaoelBLQaQ/ZBfhlG0MnG0cmTYScPpk7eDdGDWUcFUmg==", - "license": "Apache-2.0", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-4.0.1.tgz", + "integrity": "sha512-OPZBg8y5Vc9yZjmWCHrlWPMBqW5yd8+wFNl+thMdtcWz3vjVSoJQutF8YkrzI0SLGnkuFof4HSsWUhXrf219Lw==", "dependencies": { - "@sigstore/protobuf-specs": "^0.4.1", - "tuf-js": "^3.0.1" + "@sigstore/protobuf-specs": "^0.5.0", + "tuf-js": "^4.1.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sigstore/verify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-2.1.1.tgz", - "integrity": "sha512-hVJD77oT67aowHxwT4+M6PGOp+E2LtLdTK3+FC0lBO9T7sYwItDMXZ7Z07IDCvR1M717a4axbIWckrW67KMP/w==", - "license": "Apache-2.0", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-3.1.0.tgz", + "integrity": "sha512-mNe0Iigql08YupSOGv197YdHpPPr+EzDZmfCgMc7RPNaZTw5aLN01nBl6CHJOh3BGtnMIj83EeN4butBchc8Ag==", "dependencies": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.4.1" + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sinonjs/commons": { @@ -7175,10 +7759,9 @@ "devOptional": true }, "node_modules/@standard-schema/spec": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", - "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", - "license": "MIT", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "optional": true }, "node_modules/@tsconfig/node10": { @@ -7209,43 +7792,51 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", - "license": "MIT", "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/@tufjs/models": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-3.0.1.tgz", - "integrity": "sha512-UUYHISyhCU3ZgN8yaear3cGATHb3SMuKHsQ/nVbHXcmnBf+LzQ/cQfhNG+rfaSHgqGKNEm2cOCLVLELStUQ1JA==", - "license": "MIT", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-4.1.0.tgz", + "integrity": "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww==", "dependencies": { "@tufjs/canonical-json": "2.0.0", - "minimatch": "^9.0.5" + "minimatch": "^10.1.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@tufjs/models/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/@tufjs/models/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/@tufjs/models/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "license": "ISC", + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.3.tgz", + "integrity": "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -7305,7 +7896,6 @@ "version": "3.5.13", "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", - "license": "MIT", "dependencies": { "@types/node": "*" } @@ -7322,7 +7912,6 @@ "version": "1.5.4", "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", - "license": "MIT", "dependencies": { "@types/express-serve-static-core": "*", "@types/node": "*" @@ -7432,7 +8021,6 @@ "version": "1.3.14", "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", - "license": "MIT", "dependencies": { "@types/node": "*" } @@ -7458,8 +8046,7 @@ "node_modules/@types/retry": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", - "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", - "license": "MIT" + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==" }, "node_modules/@types/send": { "version": "1.2.1", @@ -7474,7 +8061,6 @@ "version": "1.9.4", "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", - "license": "MIT", "dependencies": { "@types/express": "*" } @@ -7516,7 +8102,6 @@ "version": "0.3.36", "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", - "license": "MIT", "dependencies": { "@types/node": "*" } @@ -7532,16 +8117,6 @@ "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", - "optional": true, "dependencies": { "@types/node": "*" } @@ -7726,24 +8301,35 @@ "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz", + "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==", "dev": true, - "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -7811,7 +8397,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.1.0.tgz", "integrity": "sha512-dOxxrhgyDIEUADhb/8OlV9JIqYLgos03YorAueTIeOUskLJSEsfwCByjbu98ctXitUN3znXKp0bYD/WHSudCeA==", - "license": "MIT", "engines": { "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, @@ -7983,12 +8568,11 @@ "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==" }, "node_modules/abbrev": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", - "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", - "license": "ISC", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/accepts": { @@ -8046,23 +8630,12 @@ "node": ">= 14" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "optional": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -8092,9 +8665,9 @@ } }, "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -8147,15 +8720,14 @@ } }, "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "optional": true, + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", "dependencies": { - "type-fest": "^0.21.3" + "environment": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -8168,7 +8740,6 @@ "engines": [ "node >= 0.8.0" ], - "license": "Apache-2.0", "bin": { "ansi-html": "bin/ansi-html" } @@ -8243,14 +8814,12 @@ "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, "node_modules/asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "license": "MIT", "optional": true, "dependencies": { "safer-buffer": "~2.1.0" @@ -8260,7 +8829,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "license": "MIT", "optional": true, "engines": { "node": ">=0.8" @@ -8275,15 +8843,6 @@ "node": "*" } }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "optional": true, - "engines": { - "node": ">=8" - } - }, "node_modules/async-each-series": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/async-each-series/-/async-each-series-0.1.1.tgz", @@ -8349,7 +8908,6 @@ "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "license": "Apache-2.0", "optional": true, "engines": { "node": "*" @@ -8359,27 +8917,29 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", - "license": "MIT", "optional": true }, "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", + "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", "license": "MIT", "optional": true, "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" } }, "node_modules/axios/node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", "license": "MIT", - "optional": true + "optional": true, + "engines": { + "node": ">=10" + } }, "node_modules/babel-loader": { "version": "10.0.0", @@ -8494,7 +9054,8 @@ "node_modules/balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "devOptional": true }, "node_modules/base64-js": { "version": "1.5.1", @@ -8526,10 +9087,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.8.22", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.22.tgz", - "integrity": "sha512-/tk9kky/d8T8CTXIQYASLyhAxR5VwL3zct1oAoVTaOUHwrmsGnfbRwNdEq+vOl2BN8i3PcDdP0o4Q+jjKQoFbQ==", - "license": "Apache-2.0", + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", "bin": { "baseline-browser-mapping": "dist/cli.js" } @@ -8543,7 +9103,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "license": "BSD-3-Clause", "optional": true, "dependencies": { "tweetnacl": "^0.14.3" @@ -8553,7 +9112,6 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/beasties/-/beasties-0.3.5.tgz", "integrity": "sha512-NaWu+f4YrJxEttJSm16AzMIFtVldCvaJ68b1L098KpqXmxt9xOLtKoLkKxb8ekhOrLqEJAbvT6n6SEvB/sac7A==", - "license": "Apache-2.0", "dependencies": { "css-select": "^6.0.0", "css-what": "^7.0.0", @@ -8597,22 +9155,22 @@ "optional": true }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", "dependencies": { - "bytes": "3.1.2", + "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", + "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" + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", @@ -8627,10 +9185,29 @@ "ms": "2.0.0" } }, + "node_modules/body-parser/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "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" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/body-parser/node_modules/on-finished": { "version": "2.4.1", @@ -8643,11 +9220,18 @@ "node": ">= 0.8" } }, + "node_modules/body-parser/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/bonjour-service": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" @@ -8656,13 +9240,12 @@ "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "license": "ISC" + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, "node_modules/bootstrap": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz", - "integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==", + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz", + "integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==", "funding": [ { "type": "github", @@ -8673,15 +9256,15 @@ "url": "https://opencollective.com/bootstrap" } ], + "license": "MIT", "peerDependencies": { - "jquery": "1.9.1 - 3", - "popper.js": "^1.16.1" + "@popperjs/core": "^2.11.8" } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "devOptional": true, "license": "MIT", "dependencies": { @@ -8891,9 +9474,9 @@ } }, "node_modules/browserslist": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", - "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "funding": [ { "type": "opencollective", @@ -8908,13 +9491,12 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.8.19", - "caniuse-lite": "^1.0.30001751", - "electron-to-chromium": "^1.5.238", - "node-releases": "^2.0.26", - "update-browserslist-db": "^1.1.4" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -8953,15 +9535,6 @@ "ieee754": "^1.1.13" } }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "optional": true, - "engines": { - "node": "*" - } - }, "node_modules/buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -8971,7 +9544,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", - "license": "MIT", "dependencies": { "run-applescript": "^7.0.0" }, @@ -8991,48 +9563,30 @@ } }, "node_modules/cacache": { - "version": "19.0.1", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", - "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", - "license": "ISC", + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.3.tgz", + "integrity": "sha512-3pUp4e8hv07k1QlijZu6Kn7c9+ZpWWk4j3F8N3xPuCExULobqJydKYOTj1FTq58srkJsXvO7LbGAH4C0ZU3WGw==", "dependencies": { - "@npmcli/fs": "^4.0.0", + "@npmcli/fs": "^5.0.0", "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^10.0.1", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", - "ssri": "^12.0.0", - "tar": "^7.4.3", - "unique-filename": "^4.0.0" + "ssri": "^13.0.0", + "unique-filename": "^5.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/cacache/node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/cacache/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, "node_modules/cacache/node_modules/p-map": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", - "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", - "license": "MIT", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", "engines": { "node": ">=18" }, @@ -9040,35 +9594,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cacache/node_modules/tar": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", - "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/cacache/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/cachedir": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz", - "integrity": "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", + "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", "optional": true, "engines": { "node": ">=6" @@ -9118,9 +9647,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001752", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001752.tgz", - "integrity": "sha512-vKUk7beoukxE47P5gcVNKkDRzXdVofotshHwfR9vmpeFKxmI5PBpgOMC18LUJUA/DvJ70Y7RveasIBraqsyO/g==", + "version": "1.0.30001770", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz", + "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==", "funding": [ { "type": "opencollective", @@ -9134,14 +9663,12 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ], - "license": "CC-BY-4.0" + ] }, "node_modules/caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "license": "Apache-2.0", "optional": true }, "node_modules/chai": { @@ -9242,12 +9769,11 @@ } }, "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "license": "ISC", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/chrome-trace-event": { @@ -9274,25 +9800,18 @@ "node": ">=8" } }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "optional": true, - "engines": { - "node": ">=6" - } - }, "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "optional": true, + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dependencies": { - "restore-cursor": "^3.1.0" + "restore-cursor": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cli-spinners": { @@ -9324,21 +9843,64 @@ } }, "node_modules/cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", "optional": true, "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" }, "engines": { - "node": ">=8" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", + "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", + "optional": true, + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "optional": true, + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/cli-width": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", @@ -9445,7 +10007,6 @@ "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "license": "MIT", "dependencies": { "mime-db": ">= 1.43.0 < 2" }, @@ -9457,7 +10018,6 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", - "license": "MIT", "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", @@ -9475,7 +10035,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -9483,38 +10042,16 @@ "node_modules/compression/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/compression/node_modules/negotiator": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -9588,37 +10125,18 @@ } }, "node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/content-disposition/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/content-type": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", @@ -9663,19 +10181,19 @@ } }, "node_modules/copy-webpack-plugin": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-13.0.1.tgz", - "integrity": "sha512-J+YV3WfhY6W/Xf9h+J1znYuqTye2xkBUIGyTPWuBAT27qajBa5mR4f8WBmfDY3YjRftT2kqZZiLi1qf0H+UOFw==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-14.0.0.tgz", + "integrity": "sha512-3JLW90aBGeaTLpM7mYQKpnVdgsUZRExY55giiZgLuX/xTQRUs1dOCwbBnWnvY6Q6rfZoXMNwzOQJCSZPppfqXA==", "license": "MIT", "dependencies": { "glob-parent": "^6.0.1", "normalize-path": "^3.0.0", "schema-utils": "^4.2.0", - "serialize-javascript": "^6.0.2", + "serialize-javascript": "^7.0.3", "tinyglobby": "^0.2.12" }, "engines": { - "node": ">= 18.12.0" + "node": ">= 20.9.0" }, "funding": { "type": "opencollective", @@ -9811,7 +10329,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-6.0.0.tgz", "integrity": "sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==", - "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", "css-what": "^7.0.0", @@ -9827,7 +10344,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-7.0.0.tgz", "integrity": "sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==", - "license": "BSD-2-Clause", "engines": { "node": ">= 6" }, @@ -9847,21 +10363,14 @@ "node": ">=4" } }, - "node_modules/custom-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", - "optional": true, - "peer": true - }, "node_modules/cypress": { - "version": "15.7.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-15.7.0.tgz", - "integrity": "sha512-1C81zKxnQckYm2XGi37rPV4rN0bzUoWhydhKdOyshJn5gJKszEx5as9VLSZI0jp0ye49QxmnbU4TtMpcD+OmGQ==", + "version": "15.16.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-15.16.0.tgz", + "integrity": "sha512-fy0M0c9xDLEp4v9y7LLKFeAQhIdDsobxDSKpD3JcZpqQefjy9TSzEyVV3HA0zu7hUi0bGHlSYlI7ASub8wgR9A==", "hasInstallScript": true, "optional": true, "dependencies": { - "@cypress/request": "^3.0.9", + "@cypress/request": "^4.0.0", "@cypress/xvfb": "^1.2.4", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", @@ -9870,26 +10379,22 @@ "blob-util": "^2.0.2", "bluebird": "^3.7.2", "buffer": "^5.7.1", - "cachedir": "^2.3.0", + "cachedir": "^2.4.0", "chalk": "^4.1.0", "ci-info": "^4.1.0", - "cli-cursor": "^3.1.0", "cli-table3": "0.6.1", "commander": "^6.2.1", "common-tags": "^1.8.0", "dayjs": "^1.10.4", "debug": "^4.3.4", - "enquirer": "^2.3.6", "eventemitter2": "6.4.7", "execa": "4.1.0", "executable": "^4.1.1", - "extract-zip": "2.0.1", - "figures": "^3.2.0", "fs-extra": "^9.1.0", "hasha": "5.2.2", "is-installed-globally": "~0.4.0", - "listr2": "^3.8.3", - "lodash": "^4.17.21", + "listr2": "^9.0.5", + "lodash": "^4.17.23", "log-symbols": "^4.0.0", "minimist": "^1.2.8", "ospath": "^1.2.2", @@ -9898,11 +10403,12 @@ "proxy-from-env": "1.0.0", "request-progress": "^3.0.0", "supports-color": "^8.1.1", - "systeminformation": "5.27.7", + "systeminformation": "^5.31.1", "tmp": "~0.2.4", "tree-kill": "1.2.2", + "tslib": "1.14.1", "untildify": "^4.0.0", - "yauzl": "^2.10.0" + "yauzl": "^3.3.1" }, "bin": { "cypress": "bin/cypress" @@ -10018,11 +10524,16 @@ "node": ">=14.14" } }, + "node_modules/cypress/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "optional": true + }, "node_modules/dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "license": "MIT", "optional": true, "dependencies": { "assert-plus": "^1.0.0" @@ -10031,16 +10542,6 @@ "node": ">=0.10" } }, - "node_modules/date-format": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.3.tgz", - "integrity": "sha512-7P3FyqDcfeznLZp2b+OMitV9Sz2lUnsT87WaTat9nVwqsBkTzPG3lPLNwW3en6F4pHUiWzr6vb8CLhjdK9bcxQ==", - "optional": true, - "peer": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/dayjs": { "version": "1.11.9", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", @@ -10091,10 +10592,9 @@ "license": "MIT" }, "node_modules/default-browser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", - "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", - "license": "MIT", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" @@ -10107,10 +10607,9 @@ } }, "node_modules/default-browser-id": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", - "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", - "license": "MIT", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", "engines": { "node": ">=18" }, @@ -10122,7 +10621,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "license": "MIT", "engines": { "node": ">=12" }, @@ -10165,7 +10663,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", "optional": true, "engines": { "node": ">=8" @@ -10174,8 +10671,7 @@ "node_modules/detect-node": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "license": "MIT" + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" }, "node_modules/dev-ip": { "version": "1.0.1", @@ -10189,18 +10685,12 @@ "node": ">= 0.8.0" } }, - "node_modules/di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", - "optional": true, - "peer": true - }, "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } @@ -10214,7 +10704,6 @@ "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", - "license": "MIT", "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" }, @@ -10222,24 +10711,10 @@ "node": ">=6" } }, - "node_modules/dom-serialize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", - "optional": true, - "peer": true, - "dependencies": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" - } - }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -10258,14 +10733,12 @@ "type": "github", "url": "https://github.com/sponsors/fb55" } - ], - "license": "BSD-2-Clause" + ] }, "node_modules/domhandler": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.3.0" }, @@ -10285,7 +10758,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -10314,12 +10786,6 @@ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "optional": true }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT" - }, "node_modules/easy-extender": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/easy-extender/-/easy-extender-2.3.4.tgz", @@ -10348,7 +10814,6 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "license": "MIT", "optional": true, "dependencies": { "jsbn": "~0.1.0", @@ -10356,13 +10821,12 @@ } }, "node_modules/echarts": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.4.3.tgz", - "integrity": "sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA==", - "license": "Apache-2.0", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-6.1.0.tgz", + "integrity": "sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==", "dependencies": { "tslib": "2.3.0", - "zrender": "5.4.4" + "zrender": "6.1.0" } }, "node_modules/echarts/node_modules/tslib": { @@ -10376,10 +10840,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "node_modules/electron-to-chromium": { - "version": "1.5.244", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.244.tgz", - "integrity": "sha512-OszpBN7xZX4vWMPJwB9illkN/znA8M36GQqQxi6MNy9axWxhOfJyZZJtSLQCpEFLHP2xK33BiWx9aIuIEXVCcw==", - "license": "ISC" + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==" }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -10411,7 +10874,6 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "license": "MIT", "optional": true, "dependencies": { "iconv-lite": "^0.6.2" @@ -10421,7 +10883,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", "optional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -10483,44 +10944,21 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "license": "MIT", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.0" }, "engines": { "node": ">=10.13.0" } }, - "node_modules/enquirer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", - "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "ansi-colors": "^4.1.1", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", - "optional": true, - "peer": true - }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, @@ -10540,7 +10978,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", - "license": "MIT", "engines": { "node": ">=18" }, @@ -10551,8 +10988,7 @@ "node_modules/err-code": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "license": "MIT" + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==" }, "node_modules/errno": { "version": "0.1.8", @@ -10592,9 +11028,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz", - "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==" }, "node_modules/es-object-atoms": { "version": "1.1.1", @@ -10623,9 +11059,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", - "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -10635,38 +11071,38 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.9", - "@esbuild/android-arm": "0.25.9", - "@esbuild/android-arm64": "0.25.9", - "@esbuild/android-x64": "0.25.9", - "@esbuild/darwin-arm64": "0.25.9", - "@esbuild/darwin-x64": "0.25.9", - "@esbuild/freebsd-arm64": "0.25.9", - "@esbuild/freebsd-x64": "0.25.9", - "@esbuild/linux-arm": "0.25.9", - "@esbuild/linux-arm64": "0.25.9", - "@esbuild/linux-ia32": "0.25.9", - "@esbuild/linux-loong64": "0.25.9", - "@esbuild/linux-mips64el": "0.25.9", - "@esbuild/linux-ppc64": "0.25.9", - "@esbuild/linux-riscv64": "0.25.9", - "@esbuild/linux-s390x": "0.25.9", - "@esbuild/linux-x64": "0.25.9", - "@esbuild/netbsd-arm64": "0.25.9", - "@esbuild/netbsd-x64": "0.25.9", - "@esbuild/openbsd-arm64": "0.25.9", - "@esbuild/openbsd-x64": "0.25.9", - "@esbuild/openharmony-arm64": "0.25.9", - "@esbuild/sunos-x64": "0.25.9", - "@esbuild/win32-arm64": "0.25.9", - "@esbuild/win32-ia32": "0.25.9", - "@esbuild/win32-x64": "0.25.9" + "@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" } }, "node_modules/esbuild-wasm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.25.9.tgz", - "integrity": "sha512-Jpv5tCSwQg18aCqCRD3oHIX/prBhXMDapIoG//A+6+dV0e7KQMGFg85ihJ5T1EeMjbZjON3TqFy0VrGAnIHLDA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.28.0.tgz", + "integrity": "sha512-5TRVKExcEmeMkccIZMzUq+Az6X2RoMAJyfl6SMMO1dMVhmvt0I2mx7gAb6zYi42n4d1ETcatFXazGKzA+aW7fg==", "license": "MIT", "bin": { "esbuild": "bin/esbuild" @@ -10689,15 +11125,6 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "optional": true, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/eslint": { "version": "9.39.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.0.tgz", @@ -11116,22 +11543,22 @@ "node_modules/exponential-backoff": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", - "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", - "license": "Apache-2.0" + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==" }, "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "dependencies": { "accepts": "^2.0.0", - "body-parser": "^2.2.0", + "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", + "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", @@ -11162,10 +11589,13 @@ } }, "node_modules/express-rate-limit": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", - "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz", + "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==", "license": "MIT", + "dependencies": { + "ip-address": "10.1.0" + }, "engines": { "node": ">= 16" }, @@ -11190,9 +11620,9 @@ } }, "node_modules/express/node_modules/body-parser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", - "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", @@ -11201,7 +11631,7 @@ "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", - "qs": "^6.14.0", + "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" }, @@ -11248,10 +11678,30 @@ "node": ">= 0.8" } }, + "node_modules/express/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/express/node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -11283,15 +11733,19 @@ } }, "node_modules/express/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/ms": { @@ -11321,62 +11775,51 @@ "node": ">= 0.8" } }, - "node_modules/express/node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/express/node_modules/raw-body": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", - "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.7.0", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.10" } }, "node_modules/express/node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", "dependencies": { - "debug": "^4.3.5", + "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", - "statuses": "^2.0.1" + "statuses": "^2.0.2" }, "engines": { "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", @@ -11386,6 +11829,10 @@ }, "engines": { "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/statuses": { @@ -11417,41 +11864,6 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "optional": true }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "optional": true, - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/extract-zip/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "optional": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -11459,7 +11871,6 @@ "engines": [ "node >=0.6.0" ], - "license": "MIT", "optional": true }, "node_modules/fast-deep-equal": { @@ -11486,7 +11897,8 @@ "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -11523,7 +11935,6 @@ "version": "0.11.4", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "license": "Apache-2.0", "dependencies": { "websocket-driver": ">=0.5.1" }, @@ -11531,30 +11942,6 @@ "node": ">=0.8.0" } }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "optional": true, - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "optional": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -11580,9 +11967,9 @@ } }, "node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -11593,7 +11980,11 @@ "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/finalhandler/node_modules/debug": { @@ -11685,21 +12076,23 @@ } }, "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "devOptional": true + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "funding": [ { "type": "individual", "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -11709,48 +12102,19 @@ } } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "license": "Apache-2.0", "optional": true, "engines": { "node": "*" } }, "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "optional": true, "dependencies": { "asynckit": "^0.4.0", @@ -11799,26 +12163,10 @@ "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", "optional": true }, - "node_modules/fs-extra": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", - "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", - "optional": true, - "peer": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/fs-minipass": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", - "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -11826,14 +12174,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "license": "ISC", - "optional": true, - "peer": true - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -11872,10 +12212,9 @@ } }, "node_modules/get-east-asian-width": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", - "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", - "license": "MIT", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", "engines": { "node": ">=18" }, @@ -11943,27 +12282,22 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "license": "MIT", "optional": true, "dependencies": { "assert-plus": "^1.0.0" } }, "node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "license": "ISC", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz", + "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", + "minimatch": "^10.1.1", "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "path-scurry": "^2.0.0" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -11984,7 +12318,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz", "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==", - "license": "Apache-2.0", "engines": { "node": ">=10.0" }, @@ -12001,25 +12334,35 @@ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" }, + "node_modules/glob/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "license": "ISC", + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.3.tgz", + "integrity": "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -12097,8 +12440,7 @@ "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "license": "MIT" + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" }, "node_modules/has-flag": { "version": "4.0.0", @@ -12173,6 +12515,14 @@ "node": ">= 0.4" } }, + "node_modules/hono": { + "version": "4.12.25", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.25.tgz", + "integrity": "sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==", + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/hosted-git-info": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", @@ -12185,20 +12535,10 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", - "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", - "license": "ISC", - "engines": { - "node": "20 || >=22" - } - }, "node_modules/hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", - "license": "MIT", "dependencies": { "inherits": "^2.0.1", "obuf": "^1.0.0", @@ -12206,10 +12546,37 @@ "wbuf": "^1.1.0" } }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/htmlparser2": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", - "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", { @@ -12217,19 +12584,17 @@ "url": "https://github.com/sponsors/fb55" } ], - "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", - "domutils": "^3.2.1", - "entities": "^6.0.0" + "domutils": "^3.2.2", + "entities": "^7.0.1" } }, "node_modules/htmlparser2/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "license": "BSD-2-Clause", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", "engines": { "node": ">=0.12" }, @@ -12240,14 +12605,12 @@ "node_modules/http-cache-semantics": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", - "license": "BSD-2-Clause" + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==" }, "node_modules/http-deceiver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", - "license": "MIT" + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==" }, "node_modules/http-errors": { "version": "2.0.0", @@ -12275,8 +12638,7 @@ "node_modules/http-parser-js": { "version": "0.5.10", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", - "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", - "license": "MIT" + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==" }, "node_modules/http-proxy": { "version": "1.18.1", @@ -12295,7 +12657,6 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "license": "MIT", "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" @@ -12332,7 +12693,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", - "license": "MIT", "optional": true, "dependencies": { "assert-plus": "^1.0.0", @@ -12369,7 +12729,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", - "license": "MIT", "engines": { "node": ">=10.18" } @@ -12431,7 +12790,6 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-8.0.0.tgz", "integrity": "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==", - "license": "ISC", "dependencies": { "minimatch": "^10.0.3" }, @@ -12439,16 +12797,35 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/ignore-walk/node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", - "license": "BlueOak-1.0.0", + "node_modules/ignore-walk/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/ignore-walk/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "license": "MIT", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "balanced-match": "^4.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" + } + }, + "node_modules/ignore-walk/node_modules/minimatch": { + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.3.tgz", + "integrity": "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -12468,10 +12845,11 @@ } }, "node_modules/immutable": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz", - "integrity": "sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.3.tgz", + "integrity": "sha512-AUY/VyX0E5XlibOmWt10uabJzam1zlYjwiEgQSDc5+UIkFNaF9WM0JxXKaNMGf+F/ffUF+7kRKXM9A7C0xXqMg==", "devOptional": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -12507,28 +12885,6 @@ "node": ">=0.8.19" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "license": "ISC", - "optional": true, - "peer": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -12538,16 +12894,14 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz", "integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==", - "license": "ISC", "engines": { "node": "^18.17.0 || >=20.5.0" } }, "node_modules/ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", - "license": "MIT", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", "engines": { "node": ">= 12" } @@ -12596,7 +12950,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "license": "MIT", "bin": { "is-docker": "cli.js" }, @@ -12638,7 +12991,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "license": "MIT", "dependencies": { "is-docker": "^3.0.0" }, @@ -12684,7 +13036,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz", "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==", - "license": "MIT", "engines": { "node": ">=16" }, @@ -12763,7 +13114,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "license": "MIT", "optional": true }, "node_modules/is-unicode-supported": { @@ -12785,10 +13135,9 @@ "license": "MIT" }, "node_modules/is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", - "license": "MIT", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", "dependencies": { "is-inside-container": "^1.0.0" }, @@ -12799,18 +13148,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/isbinaryfile": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.8.tgz", - "integrity": "sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w==", - "optional": true, - "peer": true, - "engines": { - "node": ">= 8.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/gjtorikian/" - } + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "node_modules/isexe": { "version": "2.0.0", @@ -12830,14 +13171,12 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "license": "MIT", "optional": true }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "license": "BSD-3-Clause", "engines": { "node": ">=8" } @@ -12846,7 +13185,6 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "license": "BSD-3-Clause", "dependencies": { "@babel/core": "^7.23.9", "@babel/parser": "^7.23.9", @@ -12858,26 +13196,10 @@ "node": ">=10" } }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, "node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "license": "MIT", "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -12891,7 +13213,6 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -12911,10 +13232,9 @@ } }, "node_modules/joi": { - "version": "18.0.1", - "resolved": "https://registry.npmjs.org/joi/-/joi-18.0.1.tgz", - "integrity": "sha512-IiQpRyypSnLisQf3PwuN2eIHAsAIGZIrLZkd4zdvIar2bDyhM91ubRjy8a3eYablXsh9BeI/c7dmPYHca5qtoA==", - "license": "BSD-3-Clause", + "version": "18.2.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-18.2.1.tgz", + "integrity": "sha512-2/OKlogiESf2Nh3TFCrRjrr9z1DRHeW0I+KReF67+4J0Ns+8hBtHRmoWAZ2OFU6I5+TWLEe6sVlSdXPjHm5UbQ==", "optional": true, "dependencies": { "@hapi/address": "^5.1.1", @@ -12923,17 +13243,20 @@ "@hapi/pinpoint": "^2.0.1", "@hapi/tlds": "^1.1.1", "@hapi/topo": "^6.0.2", - "@standard-schema/spec": "^1.0.0" + "@standard-schema/spec": "^1.1.0" }, "engines": { "node": ">= 20" } }, - "node_modules/jquery": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", - "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==", - "peer": true + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } }, "node_modules/js-tokens": { "version": "4.0.0", @@ -12942,10 +13265,19 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "license": "MIT", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "dependencies": { "argparse": "^2.0.1" }, @@ -12957,7 +13289,6 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "license": "MIT", "optional": true }, "node_modules/jsesc": { @@ -12988,13 +13319,19 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "license": "(AFL-2.1 OR BSD-3-Clause)", "optional": true }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -13007,7 +13344,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "license": "ISC", "optional": true }, "node_modules/json5": { @@ -13042,7 +13378,7 @@ "node_modules/jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", "engines": [ "node >= 0.2.0" ] @@ -13054,7 +13390,6 @@ "engines": [ "node >=0.6.0" ], - "license": "MIT", "optional": true, "dependencies": { "assert-plus": "1.0.0", @@ -13069,46 +13404,6 @@ "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", "optional": true }, - "node_modules/karma": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", - "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@colors/colors": "1.5.0", - "body-parser": "^1.19.0", - "braces": "^3.0.2", - "chokidar": "^3.5.1", - "connect": "^3.7.0", - "di": "^0.0.1", - "dom-serialize": "^2.2.1", - "glob": "^7.1.7", - "graceful-fs": "^4.2.6", - "http-proxy": "^1.18.1", - "isbinaryfile": "^4.0.8", - "lodash": "^4.17.21", - "log4js": "^6.4.1", - "mime": "^2.5.2", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.5", - "qjobs": "^1.2.0", - "range-parser": "^1.2.1", - "rimraf": "^3.0.2", - "socket.io": "^4.7.2", - "source-map": "^0.6.1", - "tmp": "^0.2.1", - "ua-parser-js": "^0.7.30", - "yargs": "^16.1.1" - }, - "bin": { - "karma": "bin/karma" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/karma-source-map-support": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", @@ -13117,102 +13412,6 @@ "source-map-support": "^0.5.5" } }, - "node_modules/karma/node_modules/connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "optional": true, - "peer": true, - "dependencies": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/karma/node_modules/connect/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "optional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/karma/node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "optional": true, - "peer": true, - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/karma/node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "optional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/karma/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", - "optional": true, - "peer": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/karma/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "optional": true, - "peer": true - }, - "node_modules/karma/node_modules/tmp": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", - "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=14.14" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -13233,13 +13432,12 @@ } }, "node_modules/launch-editor": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.12.0.tgz", - "integrity": "sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==", - "license": "MIT", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.14.1.tgz", + "integrity": "sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==", "dependencies": { "picocolors": "^1.1.1", - "shell-quote": "^1.8.3" + "shell-quote": "^1.8.4" } }, "node_modules/lazy-ass": { @@ -13358,44 +13556,102 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "node_modules/listr2": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", - "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", "optional": true, "dependencies": { - "cli-truncate": "^2.1.0", - "colorette": "^2.0.16", - "log-update": "^4.0.0", - "p-map": "^4.0.0", - "rfdc": "^1.3.0", - "rxjs": "^7.5.1", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" + "cli-truncate": "^5.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" }, "engines": { - "node": ">=10.0.0" + "node": ">=20.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "optional": true, + "engines": { + "node": ">=12" }, - "peerDependencies": { - "enquirer": ">= 2.3.0 < 3" + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "optional": true, + "engines": { + "node": ">=12" }, - "peerDependenciesMeta": { - "enquirer": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "optional": true + }, + "node_modules/listr2/node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "optional": true + }, + "node_modules/listr2/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "optional": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "optional": true, + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/listr2/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "optional": true, "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" @@ -13406,7 +13662,6 @@ "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.4.2.tgz", "integrity": "sha512-nwVGUfTBUwJKXd6lRV8pFNfnrCC1+l49ESJRM19t/tFb/97QfJEixe5DYRvug5JO7DSFKoKaVy7oGMt5rVqZvg==", "hasInstallScript": true, - "license": "MIT", "optional": true, "dependencies": { "msgpackr": "^1.11.2", @@ -13429,11 +13684,15 @@ } }, "node_modules/loader-runner": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", - "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "engines": { "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/loader-utils": { @@ -13461,10 +13720,11 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "devOptional": true + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "devOptional": true, + "license": "MIT" }, "node_modules/lodash.debounce": { "version": "4.0.8", @@ -13514,55 +13774,123 @@ } }, "node_modules/log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", - "optional": true, + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", "dependencies": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==" + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/log-update/node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "optional": true, + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/log4js": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.4.1.tgz", - "integrity": "sha512-iUiYnXqAmNKiIZ1XSAitQ4TmNs8CdZYTAWINARF3LjnsLN8tY5m0vRwd6uuWj/yNY0YHxeZodnbmxKFUOM2rMg==", - "optional": true, - "peer": true, + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dependencies": { - "date-format": "^4.0.3", - "debug": "^4.3.3", - "flatted": "^3.2.4", - "rfdc": "^1.3.0", - "streamroller": "^3.0.2" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/loupe": { @@ -13574,6 +13902,14 @@ "get-func-name": "^2.0.1" } }, + "node_modules/lru-cache": { + "version": "11.2.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", + "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", @@ -13624,36 +13960,42 @@ "dev": true }, "node_modules/make-fetch-happen": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", - "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", - "license": "ISC", + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.3.tgz", + "integrity": "sha512-iyyEpDty1mwW3dGlYXAJqC/azFn5PPvgKVwXayOGBSmKLxhKZ9fg4qIan2ePpp1vJIwfFiO34LAPZgq9SZW9Aw==", "dependencies": { - "@npmcli/agent": "^3.0.0", - "cacache": "^19.0.1", + "@npmcli/agent": "^4.0.0", + "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", - "minipass-fetch": "^4.0.0", + "minipass-fetch": "^5.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", - "proc-log": "^5.0.0", + "proc-log": "^6.0.0", "promise-retry": "^2.0.1", - "ssri": "^12.0.0" + "ssri": "^13.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/make-fetch-happen/node_modules/negotiator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", "engines": { "node": ">= 0.6" } }, + "node_modules/make-fetch-happen/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/map-stream": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", @@ -13671,17 +14013,24 @@ "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "engines": { "node": ">= 0.6" } }, "node_modules/memfs": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.50.0.tgz", - "integrity": "sha512-N0LUYQMUA1yS5tJKmMtU9yprPm6ZIg24yr/OVv/7t6q0kKDIho4cBbXRi1XKttUmNYDYgF/q45qrKE/UhGO0CA==", - "license": "Apache-2.0", + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.56.10.tgz", + "integrity": "sha512-eLvzyrwqLHnLYalJP7YZ3wBe79MXktMdfQbvMrVD80K+NhrIukCVBvgP30zTJYEEDh9hZ/ep9z0KOdD7FSHo7w==", "dependencies": { + "@jsonjoy.com/fs-core": "4.56.10", + "@jsonjoy.com/fs-fsa": "4.56.10", + "@jsonjoy.com/fs-node": "4.56.10", + "@jsonjoy.com/fs-node-builtins": "4.56.10", + "@jsonjoy.com/fs-node-to-fsa": "4.56.10", + "@jsonjoy.com/fs-node-utils": "4.56.10", + "@jsonjoy.com/fs-print": "4.56.10", + "@jsonjoy.com/fs-snapshot": "4.56.10", "@jsonjoy.com/json-pack": "^1.11.0", "@jsonjoy.com/util": "^1.9.0", "glob-to-regex.js": "^1.0.1", @@ -13692,6 +14041,9 @@ "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, "node_modules/merge-descriptors": { @@ -13723,7 +14075,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -13740,19 +14091,6 @@ "node": ">=8.6" } }, - "node_modules/mime": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", - "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", - "optional": true, - "peer": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -13785,7 +14123,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "license": "MIT", "engines": { "node": ">=18" }, @@ -13819,9 +14156,9 @@ "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.4.tgz", + "integrity": "sha512-twmL+S8+7yIsE9wsqgzU3E8/LumN3M3QELrBZ20OdmQ9jB2JvW5oZtBEmft84k/Gs5CG9mqtWc6Y9vW+JEzGxw==", "devOptional": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -13843,7 +14180,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } @@ -13852,7 +14188,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", - "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -13861,17 +14196,16 @@ } }, "node_modules/minipass-fetch": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", - "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", - "license": "MIT", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.0.tgz", + "integrity": "sha512-fiCdUALipqgPWrOVTz9fw0XhcazULXOSU6ie40DDbX1F49p1dBrSRBuswndTx1x3vEb/g0FT7vC4c4C2u/mh3A==", "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", "minizlib": "^3.0.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" }, "optionalDependencies": { "encoding": "^0.1.13" @@ -13881,7 +14215,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -13893,7 +14226,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -13901,11 +14233,15 @@ "node": ">=8" } }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/minipass-pipeline": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -13917,7 +14253,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -13925,11 +14260,15 @@ "node": ">=8" } }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/minipass-sized": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -13941,7 +14280,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -13949,11 +14287,15 @@ "node": ">=8" } }, + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/minizlib": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", - "license": "MIT", "dependencies": { "minipass": "^7.1.2" }, @@ -13967,19 +14309,6 @@ "integrity": "sha512-r6lj77KlwqLhIUku9UWYes7KJtsczvolZkzp8hbaDPPaE24OmWl5s539Mytlj22siEQKosZ26qCBgda2PKwoJw==", "devOptional": true }, - "node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "optional": true, - "peer": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/mock-socket": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz", @@ -13993,7 +14322,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", - "license": "MIT", "engines": { "node": ">=10" } @@ -14004,10 +14332,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/msgpackr": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.5.tgz", - "integrity": "sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==", - "license": "MIT", + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.8.tgz", + "integrity": "sha512-bC4UGzHhVvgDNS7kn9tV8fAucIYUBuGojcaLiz7v+P63Lmtm0Xeji8B/8tYKddALXxJLpwIeBmUN3u64C4YkRA==", "optional": true, "optionalDependencies": { "msgpackr-extract": "^3.0.2" @@ -14018,7 +14345,6 @@ "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", "hasInstallScript": true, - "license": "MIT", "optional": true, "dependencies": { "node-gyp-build-optional-packages": "5.2.2" @@ -14039,7 +14365,6 @@ "version": "7.2.5", "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", - "license": "MIT", "dependencies": { "dns-packet": "^5.2.2", "thunky": "^1.0.2" @@ -14210,47 +14535,44 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", - "license": "MIT", "optional": true }, "node_modules/node-forge": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz", - "integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { "node": ">= 6.13.0" } }, "node_modules/node-gyp": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz", - "integrity": "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==", - "license": "MIT", + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.2.0.tgz", + "integrity": "sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ==", "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^14.0.3", - "nopt": "^8.0.0", - "proc-log": "^5.0.0", + "make-fetch-happen": "^15.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.5", - "tar": "^7.4.3", + "tar": "^7.5.4", "tinyglobby": "^0.2.12", - "which": "^5.0.0" + "which": "^6.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/node-gyp-build-optional-packages": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", - "license": "MIT", "optional": true, "dependencies": { "detect-libc": "^2.0.1" @@ -14261,45 +14583,26 @@ "node-gyp-build-optional-packages-test": "build-test.js" } }, - "node_modules/node-gyp/node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/node-gyp/node_modules/isexe": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "license": "ISC", "engines": { "node": ">=16" } }, - "node_modules/node-gyp/node_modules/tar": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", - "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, + "node_modules/node-gyp/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", "engines": { - "node": ">=18" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/node-gyp/node_modules/which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", - "license": "ISC", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.0.tgz", + "integrity": "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==", "dependencies": { "isexe": "^3.1.1" }, @@ -14307,16 +14610,7 @@ "node-which": "bin/which.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/node-gyp/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/node-releases": { @@ -14326,18 +14620,17 @@ "license": "MIT" }, "node_modules/nopt": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", - "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", - "license": "ISC", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", "dependencies": { - "abbrev": "^3.0.0" + "abbrev": "^4.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/normalize-path": { @@ -14358,36 +14651,33 @@ } }, "node_modules/npm-bundled": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-4.0.0.tgz", - "integrity": "sha512-IxaQZDMsqfQ2Lz37VvyyEtKLe8FsRZuysmedy/N06TU1RyVppYKXrO4xIhR0F+7ubIBox6Q7nir6fQI3ej39iA==", - "license": "ISC", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-5.0.0.tgz", + "integrity": "sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw==", "dependencies": { - "npm-normalize-package-bin": "^4.0.0" + "npm-normalize-package-bin": "^5.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-install-checks": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-7.1.2.tgz", - "integrity": "sha512-z9HJBCYw9Zr8BqXcllKIs5nI+QggAImbBdHphOzVYrz2CB4iQ6FzWyKmlqDZua+51nAu7FcemlbTc9VgQN5XDQ==", - "license": "BSD-2-Clause", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-8.0.0.tgz", + "integrity": "sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA==", "dependencies": { "semver": "^7.1.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-normalize-package-bin": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", - "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", - "license": "ISC", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-5.0.0.tgz", + "integrity": "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-package-arg": { @@ -14409,7 +14699,6 @@ "version": "10.0.3", "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.3.tgz", "integrity": "sha512-zPukTwJMOu5X5uvm0fztwS5Zxyvmk38H/LfidkOMt3gbZVCyro2cD/ETzwzVPcWZA3JOyPznfUN/nkyFiyUbxg==", - "license": "ISC", "dependencies": { "ignore-walk": "^8.0.0", "proc-log": "^6.0.0" @@ -14419,112 +14708,51 @@ } }, "node_modules/npm-packlist/node_modules/proc-log": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.0.0.tgz", - "integrity": "sha512-KG/XsTDN901PNfPfAMmj6N/Ywg9tM+bHK8pAz+27fS4N4Pcr+4zoYBOcGSBu6ceXYNPxkLpa4ohtfxV1XcLAfA==", - "license": "ISC", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", "engines": { "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-pick-manifest": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-10.0.0.tgz", - "integrity": "sha512-r4fFa4FqYY8xaM7fHecQ9Z2nE9hgNfJR+EmoKv0+chvzWkBcORX3r0FpTByP+CbOVJDladMXnPQGVN8PBLGuTQ==", - "license": "ISC", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-11.0.3.tgz", + "integrity": "sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ==", "dependencies": { - "npm-install-checks": "^7.1.0", - "npm-normalize-package-bin": "^4.0.0", - "npm-package-arg": "^12.0.0", + "npm-install-checks": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "npm-package-arg": "^13.0.0", "semver": "^7.3.5" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm-pick-manifest/node_modules/hosted-git-info": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", - "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", - "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm-pick-manifest/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, - "node_modules/npm-pick-manifest/node_modules/npm-package-arg": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", - "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", - "license": "ISC", - "dependencies": { - "hosted-git-info": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^6.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-registry-fetch": { - "version": "18.0.2", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-18.0.2.tgz", - "integrity": "sha512-LeVMZBBVy+oQb5R6FDV9OlJCcWDU+al10oKpe+nsvcHnG24Z3uM3SvJYKfGJlfGjVU8v9liejCrUR/M5HO5NEQ==", - "license": "ISC", + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-19.1.1.tgz", + "integrity": "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw==", "dependencies": { - "@npmcli/redact": "^3.0.0", + "@npmcli/redact": "^4.0.0", "jsonparse": "^1.3.1", - "make-fetch-happen": "^14.0.0", + "make-fetch-happen": "^15.0.0", "minipass": "^7.0.2", - "minipass-fetch": "^4.0.0", + "minipass-fetch": "^5.0.0", "minizlib": "^3.0.1", - "npm-package-arg": "^12.0.0", - "proc-log": "^5.0.0" + "npm-package-arg": "^13.0.0", + "proc-log": "^6.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm-registry-fetch/node_modules/hosted-git-info": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", - "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", - "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" - }, + "node_modules/npm-registry-fetch/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm-registry-fetch/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, - "node_modules/npm-registry-fetch/node_modules/npm-package-arg": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", - "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", - "license": "ISC", - "dependencies": { - "hosted-git-info": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^6.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-run-path": { @@ -14543,7 +14771,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0" }, @@ -14574,8 +14801,7 @@ "node_modules/obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "license": "MIT" + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" }, "node_modules/on-finished": { "version": "2.3.0", @@ -14593,7 +14819,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -14625,7 +14850,6 @@ "version": "10.2.0", "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", - "license": "MIT", "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", @@ -14725,21 +14949,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/ora/node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ora/node_modules/emoji-regex": { "version": "10.6.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", @@ -14786,49 +14995,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora/node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/ora/node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", @@ -14862,10 +15028,9 @@ } }, "node_modules/ordered-binary": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.0.tgz", - "integrity": "sha512-IQh2aMfMIDbPjI/8a3Edr+PiOpcsB7yo8NdW7aHWVaoR/pcDldunMvnnwbk/auPGqmKeAdxtZl7MHX/QmPwhvQ==", - "license": "MIT", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.1.tgz", + "integrity": "sha512-QkCdPooczexPLiXIrbVOPYkR3VO3T6v2OyKRkR1Xbhpy7/LAVXwahnRCgRp78Oe/Ehf0C/HATAxfSr6eA1oX+w==", "optional": true }, "node_modules/ospath": { @@ -14913,26 +15078,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "optional": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-retry": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", - "license": "MIT", "dependencies": { "@types/retry": "0.12.2", "is-network-error": "^1.0.0", @@ -14949,7 +15098,6 @@ "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "license": "MIT", "engines": { "node": ">= 4" } @@ -14962,35 +15110,28 @@ "node": ">=6" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "license": "BlueOak-1.0.0" - }, "node_modules/pacote": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.0.0.tgz", - "integrity": "sha512-lcqexq73AMv6QNLo7SOpz0JJoaGdS3rBFgF122NZVl1bApo2mfu+XzUBU/X/XsiJu+iUmKpekRayqQYAs+PhkA==", - "license": "ISC", + "version": "21.0.4", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.0.4.tgz", + "integrity": "sha512-RplP/pDW0NNNDh3pnaoIWYPvNenS7UqMbXyvMqJczosiFWTeGGwJC2NQBLqKf4rGLFfwCOnntw1aEp9Jiqm1MA==", "dependencies": { - "@npmcli/git": "^6.0.0", - "@npmcli/installed-package-contents": "^3.0.0", - "@npmcli/package-json": "^6.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "@npmcli/run-script": "^9.0.0", - "cacache": "^19.0.0", + "@npmcli/git": "^7.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "@npmcli/run-script": "^10.0.0", + "cacache": "^20.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", - "npm-package-arg": "^12.0.0", - "npm-packlist": "^10.0.0", - "npm-pick-manifest": "^10.0.0", - "npm-registry-fetch": "^18.0.0", - "proc-log": "^5.0.0", + "npm-package-arg": "^13.0.0", + "npm-packlist": "^10.0.1", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", "promise-retry": "^2.0.1", - "sigstore": "^3.0.0", - "ssri": "^12.0.0", - "tar": "^6.1.11" + "sigstore": "^4.0.0", + "ssri": "^13.0.0", + "tar": "^7.4.3" }, "bin": { "pacote": "bin/index.js" @@ -14999,37 +15140,12 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/pacote/node_modules/hosted-git-info": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", - "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", - "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" - }, + "node_modules/pacote/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/pacote/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, - "node_modules/pacote/node_modules/npm-package-arg": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", - "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", - "license": "ISC", - "dependencies": { - "hosted-git-info": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^6.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/parent-module": { @@ -15073,7 +15189,6 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", - "license": "MIT", "dependencies": { "entities": "^6.0.0" }, @@ -15085,7 +15200,6 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-8.0.0.tgz", "integrity": "sha512-wzh11mj8KKkno1pZEu+l2EVeWsuKDfR5KNWZOTsslfUX8lPDZx77m9T0kIoAVkFtD1nx6YF8oh4BnPHvxMtNMw==", - "license": "MIT", "dependencies": { "entities": "^6.0.0", "parse5": "^8.0.0", @@ -15099,7 +15213,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, @@ -15111,7 +15224,6 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-8.0.0.tgz", "integrity": "sha512-/dQ8UzHZwnrzs3EvDj6IkKrD/jIZyTlB+8XrHJvcjNgRdmWruNdN9i9RK/JtxakmlUdPwKubKPTCqvbTgzGhrw==", - "license": "MIT", "dependencies": { "parse5": "^8.0.0" }, @@ -15123,7 +15235,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, @@ -15147,16 +15258,6 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -15171,31 +15272,24 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "license": "BlueOak-1.0.0", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", + "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "license": "MIT", "funding": { "type": "opencollective", @@ -15230,7 +15324,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "license": "MIT", "optional": true }, "node_modules/picocolors": { @@ -15240,9 +15333,10 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -15263,7 +15357,6 @@ "version": "5.1.3", "resolved": "https://registry.npmjs.org/piscina/-/piscina-5.1.3.tgz", "integrity": "sha512-0u3N7H4+hbr40KjuVn2uNhOcthu/9usKhnw5vT3J7ply79v3D3M8naI00el9Klcy16x557VsEkkUQaHCWFXC/g==", - "license": "MIT", "engines": { "node": ">=20.x" }, @@ -15272,9 +15365,9 @@ } }, "node_modules/pkce-challenge": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", - "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", "license": "MIT", "engines": { "node": ">=16.20.0" @@ -15288,17 +15381,6 @@ "node": ">=10.13.0" } }, - "node_modules/popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", - "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, "node_modules/portscanner": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.2.0.tgz", @@ -15323,9 +15405,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", + "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", "funding": [ { "type": "opencollective", @@ -15383,8 +15465,7 @@ "node_modules/postcss-media-query-parser": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", - "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", - "license": "MIT" + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==" }, "node_modules/postcss-modules-extract-imports": { "version": "3.1.0", @@ -15528,7 +15609,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", - "license": "MIT", "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" @@ -15592,20 +15672,11 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, "engines": { "node": ">=6" } }, - "node_modules/qjobs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.9" - } - }, "node_modules/qrcode": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.1.tgz", @@ -15645,11 +15716,11 @@ } }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -15677,14 +15748,6 @@ } ] }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -15694,37 +15757,61 @@ } }, "node_modules/raw-body": { - "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==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" } }, - "node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/readable-stream/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "node_modules/raw-body/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } }, "node_modules/readdirp": { "version": "3.6.0", @@ -15901,23 +15988,49 @@ "devOptional": true }, "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "optional": true, + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "license": "MIT", "engines": { "node": ">= 4" } @@ -15937,49 +16050,10 @@ "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "license": "MIT" }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "optional": true, - "peer": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", - "optional": true, - "peer": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/rollup": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.3.tgz", - "integrity": "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -15992,28 +16066,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.52.3", - "@rollup/rollup-android-arm64": "4.52.3", - "@rollup/rollup-darwin-arm64": "4.52.3", - "@rollup/rollup-darwin-x64": "4.52.3", - "@rollup/rollup-freebsd-arm64": "4.52.3", - "@rollup/rollup-freebsd-x64": "4.52.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.52.3", - "@rollup/rollup-linux-arm-musleabihf": "4.52.3", - "@rollup/rollup-linux-arm64-gnu": "4.52.3", - "@rollup/rollup-linux-arm64-musl": "4.52.3", - "@rollup/rollup-linux-loong64-gnu": "4.52.3", - "@rollup/rollup-linux-ppc64-gnu": "4.52.3", - "@rollup/rollup-linux-riscv64-gnu": "4.52.3", - "@rollup/rollup-linux-riscv64-musl": "4.52.3", - "@rollup/rollup-linux-s390x-gnu": "4.52.3", - "@rollup/rollup-linux-x64-gnu": "4.52.3", - "@rollup/rollup-linux-x64-musl": "4.52.3", - "@rollup/rollup-openharmony-arm64": "4.52.3", - "@rollup/rollup-win32-arm64-msvc": "4.52.3", - "@rollup/rollup-win32-ia32-msvc": "4.52.3", - "@rollup/rollup-win32-x64-gnu": "4.52.3", - "@rollup/rollup-win32-x64-msvc": "4.52.3", + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" } }, @@ -16060,7 +16137,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", - "license": "MIT", "engines": { "node": ">=18" }, @@ -16106,9 +16182,23 @@ } }, "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, "node_modules/safer-buffer": { "version": "2.1.2", @@ -16191,9 +16281,9 @@ } }, "node_modules/sass/node_modules/immutable": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", - "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", + "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", "license": "MIT" }, "node_modules/sass/node_modules/readdirp": { @@ -16236,9 +16326,9 @@ } }, "node_modules/schema-utils/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -16277,14 +16367,12 @@ "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", - "license": "MIT" + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==" }, "node_modules/selfsigned": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", - "license": "MIT", "dependencies": { "@types/node-forge": "^1.3.0", "node-forge": "^1" @@ -16377,12 +16465,12 @@ } }, "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", + "integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==", "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" + "engines": { + "node": ">=20.0.0" } }, "node_modules/serve-index": { @@ -16517,10 +16605,9 @@ } }, "node_modules/shell-quote": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", - "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", - "license": "MIT", + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz", + "integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==", "engines": { "node": ">= 0.4" }, @@ -16607,20 +16694,19 @@ "optional": true }, "node_modules/sigstore": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-3.1.0.tgz", - "integrity": "sha512-ZpzWAFHIFqyFE56dXqgX/DkDRZdz+rRcjoIk/RQU4IX0wiCv1l8S7ZrXDHcCc+uaf+6o7w3h2l3g6GYG5TKN9Q==", - "license": "Apache-2.0", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-4.1.0.tgz", + "integrity": "sha512-/fUgUhYghuLzVT/gaJoeVehLCgZiUxPCPMcyVNY0lIf/cTCz58K/WTI7PefDarXxp9nUKpEwg1yyz3eSBMTtgA==", "dependencies": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.4.0", - "@sigstore/sign": "^3.1.0", - "@sigstore/tuf": "^3.1.0", - "@sigstore/verify": "^2.1.0" + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0", + "@sigstore/sign": "^4.1.0", + "@sigstore/tuf": "^4.0.1", + "@sigstore/verify": "^3.1.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/sinon": { @@ -16652,33 +16738,62 @@ } }, "node_modules/sinon/node_modules/diff": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", - "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", + "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", + "license": "BSD-3-Clause", "optional": true, "engines": { "node": ">=0.3.1" } }, "node_modules/slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", "optional": true, "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" }, "engines": { - "node": ">=8" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "optional": true, + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "license": "MIT", "engines": { "node": ">= 6.0.0", "npm": ">= 3.0.0" @@ -16728,23 +16843,48 @@ } }, "node_modules/socket.io-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", - "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", + "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", "devOptional": true, + "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" + "debug": "~4.4.1" }, "engines": { "node": ">=10.0.0" } }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", - "license": "MIT", "dependencies": { "faye-websocket": "^0.11.3", "uuid": "^8.3.2", @@ -16755,7 +16895,6 @@ "version": "2.8.7", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", - "license": "MIT", "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" @@ -16769,7 +16908,6 @@ "version": "8.0.5", "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "license": "MIT", "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", @@ -16840,7 +16978,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "license": "Apache-2.0", "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -16849,14 +16986,12 @@ "node_modules/spdx-exceptions": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "license": "CC-BY-3.0" + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==" }, "node_modules/spdx-expression-parse": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "license": "MIT", "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -16865,14 +17000,12 @@ "node_modules/spdx-license-ids": { "version": "3.0.22", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", - "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", - "license": "CC0-1.0" + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==" }, "node_modules/spdy": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "license": "MIT", "dependencies": { "debug": "^4.1.0", "handle-thing": "^2.0.0", @@ -16888,7 +17021,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "license": "MIT", "dependencies": { "debug": "^4.1.0", "detect-node": "^2.0.4", @@ -16898,20 +17030,6 @@ "wbuf": "^1.7.3" } }, - "node_modules/spdy-transport/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/split": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", @@ -16928,7 +17046,6 @@ "version": "1.18.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", - "license": "MIT", "optional": true, "dependencies": { "asn1": "~0.2.3", @@ -16951,15 +17068,14 @@ } }, "node_modules/ssri": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", - "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", - "license": "ISC", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.0.tgz", + "integrity": "sha512-yizwGBpbCn4YomB2lzhZqrHLJoqFGXihNbib3ozhqF/cIp5ue+xSmOQrjNasEE62hFxsCcg/V/z23t4n8jMEng==", "dependencies": { "minipass": "^7.0.3" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/start-server-and-test": { @@ -17054,27 +17170,12 @@ "node": ">= 0.10.0" } }, - "node_modules/streamroller": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.0.2.tgz", - "integrity": "sha512-ur6y5S5dopOaRXBuRIZ1u6GC5bcEXHRZKgfBjfCglMhmIf+roVCECjvkEYzNQOXIN2/JPnkMPW/8B3CZoKaEPA==", - "optional": true, - "peer": true, - "dependencies": { - "date-format": "^4.0.3", - "debug": "^4.1.1", - "fs-extra": "^10.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "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.1.0" + "safe-buffer": "~5.2.0" } }, "node_modules/string-width": { @@ -17090,21 +17191,6 @@ "node": ">=8" } }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -17116,19 +17202,6 @@ "node": ">=8" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -17176,10 +17249,9 @@ } }, "node_modules/systeminformation": { - "version": "5.27.7", - "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.27.7.tgz", - "integrity": "sha512-saaqOoVEEFaux4v0K8Q7caiauRwjXC4XbD2eH60dxHXbpKxQ8kH9Rf7Jh+nryKpOUSEFxtCdBlSUx0/lO6rwRg==", - "license": "MIT", + "version": "5.31.1", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.31.1.tgz", + "integrity": "sha512-6pRwxoGeV/roJYpsfcP6tN9mep6pPeCtXbUOCdVa0nme05Brwcwdge/fVNhIZn2wuUitAKZm4IYa7QjnRIa9zA==", "optional": true, "os": [ "darwin", @@ -17203,105 +17275,36 @@ } }, "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "engines": { "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "license": "ISC", + "version": "7.5.16", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz", + "integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==", "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" }, "engines": { - "node": ">=10" - } - }, - "node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "license": "ISC", - "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/tar/node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/terser": { - "version": "5.44.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", - "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", - "license": "BSD-2-Clause", + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -17316,15 +17319,14 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", + "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", "terser": "^5.31.1" }, "engines": { @@ -17353,7 +17355,6 @@ "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -17363,7 +17364,6 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.5.0.tgz", "integrity": "sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==", - "license": "MIT", "engines": { "node": ">=10.18" }, @@ -17390,8 +17390,7 @@ "node_modules/thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", - "license": "MIT" + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" }, "node_modules/tiny-emitter": { "version": "2.1.0", @@ -17432,9 +17431,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", "engines": { "node": ">=12" @@ -17447,7 +17446,6 @@ "version": "6.1.86", "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", - "license": "MIT", "optional": true, "dependencies": { "tldts-core": "^6.1.86" @@ -17460,14 +17458,8 @@ "version": "6.1.86", "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", - "license": "MIT", "optional": true }, - "node_modules/tlite": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/tlite/-/tlite-0.1.9.tgz", - "integrity": "sha512-5QOBAvDxZZwW1i+2YXMgF6/PuV/KhA0LyE9PyVi8Ywr3bfIPziZcQD+RpdJaQurCU8zIGtBo/XuPCEHdvyeFuQ==" - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -17491,7 +17483,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", - "license": "BSD-3-Clause", "optional": true, "dependencies": { "tldts": "^6.1.32" @@ -17504,7 +17495,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", - "license": "Apache-2.0", "engines": { "node": ">=10.0" }, @@ -17596,24 +17586,22 @@ "license": "0BSD" }, "node_modules/tuf-js": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-3.1.0.tgz", - "integrity": "sha512-3T3T04WzowbwV2FDiGXBbr81t64g1MUGGJRgT4x5o97N+8ArdhVCAF9IxFrxuSJmM3E5Asn7nKHkao0ibcZXAg==", - "license": "MIT", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-4.1.0.tgz", + "integrity": "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ==", "dependencies": { - "@tufjs/models": "3.0.1", - "debug": "^4.4.1", - "make-fetch-happen": "^14.0.3" + "@tufjs/models": "4.1.0", + "debug": "^4.4.3", + "make-fetch-happen": "^15.0.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/tuf-js/node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -17629,14 +17617,12 @@ "node_modules/tuf-js/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "license": "Apache-2.0", "optional": true, "dependencies": { "safe-buffer": "^5.0.1" @@ -17649,7 +17635,6 @@ "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "license": "Unlicense", "optional": true }, "node_modules/type-check": { @@ -17674,18 +17659,6 @@ "node": ">=4" } }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "optional": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -17716,26 +17689,6 @@ "node": ">=14.17" } }, - "node_modules/ua-parser-js": { - "version": "0.7.35", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.35.tgz", - "integrity": "sha512-veRf7dawaj9xaWEu9HoTVn5Pggtc/qj+kqTOFvNiN1l0YdxwC1kvel57UCjThjGa3BHBihE8/UJAHI+uQHmd/g==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - } - ], - "optional": true, - "peer": true, - "engines": { - "node": "*" - } - }, "node_modules/undici-types": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", @@ -17783,27 +17736,25 @@ } }, "node_modules/unique-filename": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", - "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", - "license": "ISC", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-5.0.0.tgz", + "integrity": "sha512-2RaJTAvAb4owyjllTfXzFClJ7WsGxlykkPvCr9pA//LD9goVq+m4PPAeBgNodGZ7nSrntT/auWpJ6Y5IFXcfjg==", "dependencies": { - "unique-slug": "^5.0.0" + "unique-slug": "^6.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/unique-slug": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", - "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", - "license": "ISC", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-6.0.0.tgz", + "integrity": "sha512-4Lup7Ezn8W3d52/xBhZBVdx323ckxa7DEvd9kPQHppTkLoJXw6ltrBCyj5pnrxj0qKDxYMJ56CoxNuFCscdTiw==", "dependencies": { "imurmurhash": "^0.1.4" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/universalify": { @@ -17833,9 +17784,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", - "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "funding": [ { "type": "opencollective", @@ -17850,7 +17801,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" @@ -17866,6 +17816,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", + "dev": true, "dependencies": { "punycode": "^2.1.0" } @@ -17901,7 +17852,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "license": "Apache-2.0", "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -17931,7 +17881,6 @@ "engines": [ "node >=0.6.0" ], - "license": "MIT", "optional": true, "dependencies": { "assert-plus": "^1.0.0", @@ -17940,12 +17889,12 @@ } }, "node_modules/vite": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.11.tgz", - "integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", + "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "license": "MIT", "dependencies": { - "esbuild": "^0.25.0", + "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", @@ -18017,7 +17966,6 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "license": "MIT", "engines": { "node": ">=12.0.0" }, @@ -18031,9 +17979,9 @@ } }, "node_modules/vite/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", "engines": { "node": ">=12" @@ -18046,7 +17994,6 @@ "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "license": "MIT", "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" @@ -18058,16 +18005,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/wait-on": { "version": "8.0.5", "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-8.0.5.tgz", @@ -18105,7 +18042,6 @@ "version": "1.7.3", "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "license": "MIT", "dependencies": { "minimalistic-assert": "^1.0.0" } @@ -18114,14 +18050,12 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", - "license": "MIT", "optional": true }, "node_modules/webpack": { - "version": "5.101.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.2.tgz", - "integrity": "sha512-4JLXU0tD6OZNVqlwzm3HGEhAHufSiyv+skb7q0d2367VDMzrU1Q/ZeepvkcHH0rZie6uqEtTQQe0OEOOluH3Mg==", - "license": "MIT", + "version": "5.105.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", + "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -18131,22 +18065,22 @@ "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.15.0", "acorn-import-phases": "^1.0.3", - "browserslist": "^4.24.0", + "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.3", - "es-module-lexer": "^1.2.1", + "enhanced-resolve": "^5.19.0", + "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", + "loader-runner": "^4.3.1", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^4.3.2", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.11", - "watchpack": "^2.4.1", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.16", + "watchpack": "^2.5.1", "webpack-sources": "^3.3.3" }, "bin": { @@ -18169,7 +18103,6 @@ "version": "7.4.2", "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz", "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==", - "license": "MIT", "dependencies": { "colorette": "^2.0.10", "memfs": "^4.6.0", @@ -18198,7 +18131,6 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -18210,7 +18142,6 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.2.tgz", "integrity": "sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==", - "license": "MIT", "dependencies": { "@types/bonjour": "^3.5.13", "@types/connect-history-api-fallback": "^1.5.4", @@ -18267,7 +18198,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", - "license": "MIT", "engines": { "node": ">=0.8" } @@ -18276,7 +18206,6 @@ "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" }, @@ -18284,26 +18213,15 @@ "node": ">= 0.6" } }, - "node_modules/webpack-dev-server/node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/webpack-dev-server/node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" }, "node_modules/webpack-dev-server/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -18312,45 +18230,43 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/webpack-dev-server/node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "license": "MIT", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "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", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "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", + "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", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -18364,17 +18280,16 @@ } }, "node_modules/webpack-dev-server/node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "license": "MIT", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "statuses": "2.0.1", + "statuses": "~2.0.2", "unpipe": "~1.0.0" }, "engines": { @@ -18382,10 +18297,9 @@ } }, "node_modules/webpack-dev-server/node_modules/ipaddr.js": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", - "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", - "license": "MIT", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", + "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==", "engines": { "node": ">= 10" } @@ -18394,7 +18308,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -18402,14 +18315,12 @@ "node_modules/webpack-dev-server/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/webpack-dev-server/node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -18418,45 +18329,23 @@ } }, "node_modules/webpack-dev-server/node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" - }, - "node_modules/webpack-dev-server/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", "license": "MIT" }, "node_modules/webpack-dev-server/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "engines": { "node": ">= 0.8" } }, "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "license": "MIT", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "engines": { "node": ">=10.0.0" }, @@ -18528,11 +18417,22 @@ "acorn": "^8.14.0" } }, + "node_modules/webpack/node_modules/watchpack": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "license": "Apache-2.0", "dependencies": { "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", @@ -18546,7 +18446,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "license": "Apache-2.0", "engines": { "node": ">=0.8.0" } @@ -18599,24 +18498,6 @@ "node": ">=8" } }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -18647,7 +18528,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", - "license": "MIT", "dependencies": { "is-wsl": "^3.1.0" }, @@ -18682,27 +18562,28 @@ "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==" }, "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "engines": { + "node": ">=18" + } }, "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "optional": true, - "peer": true, + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", + "license": "MIT", "dependencies": { - "cliui": "^7.0.2", + "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^7.2.0", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^22.0.0" }, "engines": { - "node": ">=10" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yargs-parser": { @@ -18717,64 +18598,127 @@ "node": ">=6" } }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/yargs/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/yargs/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "optional": true, - "peer": true, + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "license": "ISC", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/yargs/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "optional": true, - "peer": true, + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/yargs/node_modules/y18n": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.6.tgz", - "integrity": "sha512-PlVX4Y0lDTN6E2V4ES2tEdyvXkeKzxa8c/vo0pxPr/TqbztddTP0yn7zZylIyiAuxerqj0Q5GhpJ1YJCP8LaZQ==", - "optional": true, - "peer": true, + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", "engines": { "node": ">=10" } }, "node_modules/yargs/node_modules/yargs-parser": { - "version": "20.2.7", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz", - "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==", - "optional": true, - "peer": true, + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "license": "ISC", "engines": { - "node": ">=10" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.4.0.tgz", + "integrity": "sha512-jIH9yLR9wqr0wOS0TpBvo/g/2UgZH5qePVbjgRliiF0BYvOZyaBknKsF+x9Iht0O6sqgnB93rCICdOZFecJuDw==", "optional": true, "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" + "pend": "~1.2.0" + }, + "engines": { + "node": ">=12" } }, "node_modules/yn": { @@ -18810,21 +18754,21 @@ } }, "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, "node_modules/zod-to-json-schema": { - "version": "3.24.6", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", - "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", "license": "ISC", "peerDependencies": { - "zod": "^3.24.1" + "zod": "^3.25 || ^4" } }, "node_modules/zone.js": { @@ -18834,10 +18778,9 @@ "license": "MIT" }, "node_modules/zrender": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.4.4.tgz", - "integrity": "sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw==", - "license": "BSD-3-Clause", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-6.1.0.tgz", + "integrity": "sha512-oEGMDB6pOP2S6OwRR4PdVv610zrjnA3Bh+JnSG12fYJlBKjtNAoEb5fSUoCOOINlH96I2fU38/A2UpRKs67xYQ==", "dependencies": { "tslib": "2.3.0" } @@ -18845,8 +18788,7 @@ "node_modules/zrender/node_modules/tslib": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", - "license": "0BSD" + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" } }, "dependencies": { @@ -19010,31 +18952,31 @@ } }, "@angular-devkit/architect": { - "version": "0.2003.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2003.12.tgz", - "integrity": "sha512-5H40lAFF4CKY32C4HOp6bTlOF1f4WsGCwe7FjFQp9A+T7yoCBiHpIWt2JKTwV4sBoTKVDZOnuf0GG+UVKjQT4A==", + "version": "0.2003.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2003.25.tgz", + "integrity": "sha512-39pTqt4wSmpD1WeCee46oSGXRh6TR1PFd9GZEwyZoMvBTMs8mE2sXGxUgd4Qyi5CkQ1XnCM5XfgJcjUWUQRoGg==", "requires": { - "@angular-devkit/core": "20.3.12", + "@angular-devkit/core": "20.3.25", "rxjs": "7.8.2" }, "dependencies": { "@angular-devkit/core": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.12.tgz", - "integrity": "sha512-ReFxd/UOoVDr3+kIUjmYILQZF89qg62POdY7a7OqBH7plmInFlYVSEDouJvGqj3LVCPiqTk2ZOSChbhS/eLxXA==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.25.tgz", + "integrity": "sha512-pSfeWEoS1y9zxqTOw0Etj2NXkRmp/aU52wdnyq3KqVA9cJtgxtlOHlBBNswTil4dReojEy+0K1xJm+mLuWuLxA==", "requires": { - "ajv": "8.17.1", + "ajv": "8.18.0", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", - "picomatch": "4.0.3", + "picomatch": "4.0.4", "rxjs": "7.8.2", "source-map": "0.7.6" } }, "ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -19066,9 +19008,9 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==" + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, "readdirp": { "version": "4.1.2", @@ -19085,15 +19027,15 @@ } }, "@angular-devkit/build-angular": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-20.3.12.tgz", - "integrity": "sha512-HPepPbJA5vprYTWJaSCfpk0s1bPT6Ui6VjFOSb9oY+p9iq+MGkuB1I+swNcRcMLttyMD+FpbMd27F8jSeX5XVw==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-20.3.25.tgz", + "integrity": "sha512-jp2sbJhbVRT65RbGENY/lz3Z0W0D50Af3xzENmLDLBVp9uhlpIPSTC9LG4CEPV7T6H5Oq6ymHKM+OO5WwTlHHA==", "requires": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2003.12", - "@angular-devkit/build-webpack": "0.2003.12", - "@angular-devkit/core": "20.3.12", - "@angular/build": "20.3.12", + "@angular-devkit/architect": "0.2003.25", + "@angular-devkit/build-webpack": "0.2003.25", + "@angular-devkit/core": "20.3.25", + "@angular/build": "20.3.25", "@babel/core": "7.28.3", "@babel/generator": "7.28.3", "@babel/helper-annotate-as-pure": "7.27.3", @@ -19104,15 +19046,15 @@ "@babel/preset-env": "7.28.3", "@babel/runtime": "7.28.3", "@discoveryjs/json-ext": "0.6.3", - "@ngtools/webpack": "20.3.12", + "@ngtools/webpack": "20.3.25", "ansi-colors": "4.1.3", "autoprefixer": "10.4.21", "babel-loader": "10.0.0", "browserslist": "^4.21.5", - "copy-webpack-plugin": "13.0.1", + "copy-webpack-plugin": "14.0.0", "css-loader": "7.1.2", - "esbuild": "0.25.9", - "esbuild-wasm": "0.25.9", + "esbuild": "0.28.0", + "esbuild-wasm": "0.28.0", "fast-glob": "3.3.3", "http-proxy-middleware": "3.0.5", "istanbul-lib-instrument": "6.0.3", @@ -19125,9 +19067,9 @@ "mini-css-extract-plugin": "2.9.4", "open": "10.2.0", "ora": "8.2.0", - "picomatch": "4.0.3", + "picomatch": "4.0.4", "piscina": "5.1.3", - "postcss": "8.5.6", + "postcss": "8.5.12", "postcss-loader": "8.1.1", "resolve-url-loader": "5.0.0", "rxjs": "7.8.2", @@ -19139,7 +19081,7 @@ "terser": "5.43.1", "tree-kill": "1.2.2", "tslib": "2.8.1", - "webpack": "5.101.2", + "webpack": "5.105.0", "webpack-dev-middleware": "7.4.2", "webpack-dev-server": "5.2.2", "webpack-merge": "6.0.1", @@ -19147,22 +19089,178 @@ }, "dependencies": { "@angular-devkit/core": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.12.tgz", - "integrity": "sha512-ReFxd/UOoVDr3+kIUjmYILQZF89qg62POdY7a7OqBH7plmInFlYVSEDouJvGqj3LVCPiqTk2ZOSChbhS/eLxXA==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.25.tgz", + "integrity": "sha512-pSfeWEoS1y9zxqTOw0Etj2NXkRmp/aU52wdnyq3KqVA9cJtgxtlOHlBBNswTil4dReojEy+0K1xJm+mLuWuLxA==", "requires": { - "ajv": "8.17.1", + "ajv": "8.18.0", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", - "picomatch": "4.0.3", + "picomatch": "4.0.4", "rxjs": "7.8.2", "source-map": "0.7.6" } }, + "@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "optional": true + }, + "@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "optional": true + }, + "@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "optional": true + }, + "@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "optional": true + }, + "@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "optional": true + }, "ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -19196,6 +19294,40 @@ "ms": "^2.1.3" } }, + "esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "optional": true, + "requires": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, "http-proxy-middleware": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.5.tgz", @@ -19230,9 +19362,9 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==" + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, "readdirp": { "version": "4.1.2", @@ -19265,20 +19397,20 @@ } }, "@angular-devkit/build-webpack": { - "version": "0.2003.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2003.12.tgz", - "integrity": "sha512-IkhCU0nAsXYBQOfHu2gQBcYBKhaV1c8wYtu7MmelBcN/iUrG8hRf1sZx+ppUgsdZuBYxCiDiLpcfRVRCIASkvw==", + "version": "0.2003.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2003.25.tgz", + "integrity": "sha512-jJMpYBdWeRfvrCna7JWsyMBbvjMcPblyzh4/pSfWw5znla3hJGzDtmb84qKZ+IB8eZNEcky4iGR5LK2jdGFLAg==", "requires": { - "@angular-devkit/architect": "0.2003.12", + "@angular-devkit/architect": "0.2003.25", "rxjs": "7.8.2" } }, "@angular-devkit/schematics": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.3.12.tgz", - "integrity": "sha512-JqJ1u59y+Ud51k/8MHYzSP+aQOeC2PJBaDmMnvqfWVaIt6n3x4gc/VtuhqhpJ0SKulbFuOWgAfI6QbPFrgUYQQ==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.3.25.tgz", + "integrity": "sha512-IB0IHf8ZRqr69hT/XIfHkYLPAYHWQ/WUc6+fKHBwq58jJlm/y5QUeTEuXvJ18IX/+hUIqw+E2Q3T0N9rLDLzXg==", "requires": { - "@angular-devkit/core": "20.3.12", + "@angular-devkit/core": "20.3.25", "jsonc-parser": "3.3.1", "magic-string": "0.30.17", "ora": "8.2.0", @@ -19286,22 +19418,22 @@ }, "dependencies": { "@angular-devkit/core": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.12.tgz", - "integrity": "sha512-ReFxd/UOoVDr3+kIUjmYILQZF89qg62POdY7a7OqBH7plmInFlYVSEDouJvGqj3LVCPiqTk2ZOSChbhS/eLxXA==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.25.tgz", + "integrity": "sha512-pSfeWEoS1y9zxqTOw0Etj2NXkRmp/aU52wdnyq3KqVA9cJtgxtlOHlBBNswTil4dReojEy+0K1xJm+mLuWuLxA==", "requires": { - "ajv": "8.17.1", + "ajv": "8.18.0", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", - "picomatch": "4.0.3", + "picomatch": "4.0.4", "rxjs": "7.8.2", "source-map": "0.7.6" } }, "ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -19333,9 +19465,9 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==" + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, "readdirp": { "version": "4.1.2", @@ -19352,20 +19484,20 @@ } }, "@angular/animations": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.14.tgz", - "integrity": "sha512-Sx3/XNu2rR+R8T8JkJEaIpZDZPk0IecS0Ayt6HTanNUZXuw0HVou3vkjR5B2St5nM4MXs0gh+S6aLNuArtqJTQ==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.25.tgz", + "integrity": "sha512-lQmti3tI85D525TjUVGqCNLzFxSUoZg+vgIyvuGJZPY0UU/o2S6KAxW6ObmcRotZZHNfenLHIxWgzamBDjIjuw==", "requires": { "tslib": "^2.3.0" } }, "@angular/build": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-20.3.12.tgz", - "integrity": "sha512-iAZve4VPviC8y6RFctyh3qFXSlP5mth9K46/0zasB4LV4pcmu8BrzIHERxIn/jCDNdVdPh973kxo1ksO4WpyuA==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-20.3.25.tgz", + "integrity": "sha512-ddWmPzYuzDWz9ql0262u9w3OJHXpSjHVNIDFIYOG2XYyMj/Xve0RkJ8/xvV+Hv7T3iqyyGKtsO+n/d0GCJQCtQ==", "requires": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2003.12", + "@angular-devkit/architect": "0.2003.25", "@babel/core": "7.28.3", "@babel/helper-annotate-as-pure": "7.27.3", "@babel/helper-split-export-declaration": "7.24.7", @@ -19373,7 +19505,7 @@ "@vitejs/plugin-basic-ssl": "2.1.0", "beasties": "0.3.5", "browserslist": "^4.23.0", - "esbuild": "0.25.9", + "esbuild": "0.28.0", "https-proxy-agent": "7.0.6", "istanbul-lib-instrument": "6.0.3", "jsonc-parser": "3.3.1", @@ -19382,24 +19514,172 @@ "magic-string": "0.30.17", "mrmime": "2.0.1", "parse5-html-rewriting-stream": "8.0.0", - "picomatch": "4.0.3", + "picomatch": "4.0.4", "piscina": "5.1.3", - "rollup": "4.52.3", + "rollup": "4.59.0", "sass": "1.90.0", "semver": "7.7.2", "source-map-support": "0.5.21", "tinyglobby": "0.2.14", - "vite": "7.1.11", + "vite": "7.3.2", "watchpack": "2.4.4" }, "dependencies": { - "ansi-escapes": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", - "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", - "requires": { - "environment": "^1.0.0" - } + "@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "optional": true + }, + "@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "optional": true + }, + "@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "optional": true + }, + "@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "optional": true + }, + "@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "optional": true }, "ansi-regex": { "version": "6.2.2", @@ -19411,14 +19691,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==" }, - "cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "requires": { - "restore-cursor": "^5.0.0" - } - }, "cli-truncate": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", @@ -19433,10 +19705,43 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==" }, + "esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "requires": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, "eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==" }, "is-fullwidth-code-point": { "version": "4.0.0", @@ -19456,69 +19761,16 @@ "wrap-ansi": "^9.0.0" } }, - "log-update": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", - "requires": { - "ansi-escapes": "^7.0.0", - "cli-cursor": "^5.0.0", - "slice-ansi": "^7.1.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", - "requires": { - "get-east-asian-width": "^1.3.1" - } - }, - "slice-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", - "requires": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - } - } - } - }, - "onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "requires": { - "mimic-function": "^5.0.0" - } - }, "picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==" - }, - "restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "requires": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - } + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, "semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==" }, - "signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" - }, "slice-ansi": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", @@ -19539,11 +19791,11 @@ } }, "strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "requires": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" } }, "wrap-ansi": { @@ -19559,39 +19811,39 @@ } }, "@angular/cli": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.3.12.tgz", - "integrity": "sha512-vqVyVjbFPCRMjA5evL7tV2JeR6Anuzb9WcXTMB17fr7uzKNNAvo7KyRaOJjp+TU4JDARTNyGPy0aywfPx7R60A==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.3.25.tgz", + "integrity": "sha512-QOSxza45CZY11kqPVpqsU+WGYF99rR/r9A9GykZQWuAHb5SEAxlXHyaaGMu/BMtBHy5HNIlJH25UlzelrbaNyQ==", "requires": { - "@angular-devkit/architect": "0.2003.12", - "@angular-devkit/core": "20.3.12", - "@angular-devkit/schematics": "20.3.12", + "@angular-devkit/architect": "0.2003.25", + "@angular-devkit/core": "20.3.25", + "@angular-devkit/schematics": "20.3.25", "@inquirer/prompts": "7.8.2", "@listr2/prompt-adapter-inquirer": "3.0.1", - "@modelcontextprotocol/sdk": "1.17.3", - "@schematics/angular": "20.3.12", + "@modelcontextprotocol/sdk": "1.26.0", + "@schematics/angular": "20.3.25", "@yarnpkg/lockfile": "1.1.0", "algoliasearch": "5.35.0", "ini": "5.0.0", "jsonc-parser": "3.3.1", "listr2": "9.0.1", "npm-package-arg": "13.0.0", - "pacote": "21.0.0", + "pacote": "21.0.4", "resolve": "1.22.10", "semver": "7.7.2", "yargs": "18.0.0", - "zod": "3.25.76" + "zod": "4.1.13" }, "dependencies": { "@angular-devkit/core": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.12.tgz", - "integrity": "sha512-ReFxd/UOoVDr3+kIUjmYILQZF89qg62POdY7a7OqBH7plmInFlYVSEDouJvGqj3LVCPiqTk2ZOSChbhS/eLxXA==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.25.tgz", + "integrity": "sha512-pSfeWEoS1y9zxqTOw0Etj2NXkRmp/aU52wdnyq3KqVA9cJtgxtlOHlBBNswTil4dReojEy+0K1xJm+mLuWuLxA==", "requires": { - "ajv": "8.17.1", + "ajv": "8.18.0", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", - "picomatch": "4.0.3", + "picomatch": "4.0.4", "rxjs": "7.8.2", "source-map": "0.7.6" } @@ -19605,9 +19857,9 @@ } }, "ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -19623,14 +19875,6 @@ "ajv": "^8.0.0" } }, - "ansi-escapes": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", - "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", - "requires": { - "environment": "^1.0.0" - } - }, "ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", @@ -19651,14 +19895,6 @@ "readdirp": "^4.0.1" } }, - "cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "requires": { - "restore-cursor": "^5.0.0" - } - }, "cli-truncate": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", @@ -19668,25 +19904,15 @@ "string-width": "^7.0.0" } }, - "cliui": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", - "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", - "requires": { - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - } - }, "emoji-regex": { "version": "10.6.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==" }, "eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==" }, "is-fullwidth-code-point": { "version": "4.0.0", @@ -19711,49 +19937,10 @@ "wrap-ansi": "^9.0.0" } }, - "log-update": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", - "requires": { - "ansi-escapes": "^7.0.0", - "cli-cursor": "^5.0.0", - "slice-ansi": "^7.1.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", - "requires": { - "get-east-asian-width": "^1.3.1" - } - }, - "slice-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", - "requires": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - } - } - } - }, - "onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "requires": { - "mimic-function": "^5.0.0" - } - }, "picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==" + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, "readdirp": { "version": "4.1.2", @@ -19762,25 +19949,11 @@ "optional": true, "peer": true }, - "restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "requires": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - } - }, "semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==" }, - "signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" - }, "slice-ansi": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", @@ -19806,11 +19979,11 @@ } }, "strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "requires": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" } }, "wrap-ansi": { @@ -19822,52 +19995,29 @@ "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" - }, - "yargs": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", - "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", - "requires": { - "cliui": "^9.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "string-width": "^7.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^22.0.0" - } - }, - "yargs-parser": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", - "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==" } } }, "@angular/common": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.14.tgz", - "integrity": "sha512-OOUvjTtnpktJLsNupA+GFT2q5zNocPdpOENA8aSrXvAheNybLjgi+otO3U3sQsvB1VwaoEZ9GT5O3lZlstnA/A==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.25.tgz", + "integrity": "sha512-rnRGcXbjet0DHgkRL4Dqxk21G2T4UypVfiTV/fay58H8w9U89PJ1L6gRmk8B/uyfpii/9r23cBwnpcguQykxYw==", "requires": { "tslib": "^2.3.0" } }, "@angular/compiler": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.14.tgz", - "integrity": "sha512-KFbfPPAbclzGDujCVruflCD9j4Zwwxvrg7Y4C9GJYs3LZ85t+BfIMDDnvpBUM07ZLnfY4TO4gQdHmJAcaGGXDQ==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.25.tgz", + "integrity": "sha512-TSh6gVoQqlLPqWwsYMK0lfVEQYENQO+USzS+BHFXEHFfgBRap6qDpIUGnRdj0Y2PlaVJUVFbeq1855EZUPUEoA==", "requires": { "tslib": "^2.3.0" } }, "@angular/compiler-cli": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.14.tgz", - "integrity": "sha512-lFg9ikwRClzDPjdFiwynbVFIi1RJZf/0i+OHa3Ns2gzXxJeHNKMJrHHjWZ2DU4N2UpxH0YAPe22N9Bie28IuQQ==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.25.tgz", + "integrity": "sha512-iqxwVo5Pgzt3EfT49OZ6plxA6KKxwv7ixx1XNH7QRvaOJC9gmsPScWpx+LO7ZsVZdo/NkA+rnXDl0PauUgGciw==", "requires": { "@babel/core": "7.28.3", "@jridgewell/sourcemap-codec": "^1.4.14", @@ -19879,16 +20029,6 @@ "yargs": "^18.0.0" }, "dependencies": { - "ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==" - }, - "ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==" - }, "chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -19897,237 +20037,93 @@ "readdirp": "^4.0.1" } }, - "cliui": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", - "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", - "requires": { - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - } - }, - "emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==" - }, "readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==" - }, - "string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "requires": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - } - }, - "strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "requires": { - "ansi-regex": "^6.0.1" - } - }, - "wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "requires": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - } - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" - }, - "yargs": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", - "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", - "requires": { - "cliui": "^9.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "string-width": "^7.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^22.0.0" - } - }, - "yargs-parser": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", - "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==" } } }, "@angular/core": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.14.tgz", - "integrity": "sha512-rpyEbhWF6Fj/xI9IvNLZh5QBUYnoXuF7vX54CCtyQ2MHALxRR/aa1WRxjRM96cF2OqodQ/Gj3oYW8ei8hlBh4w==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.25.tgz", + "integrity": "sha512-B4XnnR5jzikZDvZ4PjwjAWZMT14dxrKrmJdwa/n0yp7rMPkIJTKF6ZJMg4d1pLWLLSsc2oWHioN3UrWlGqIKnA==", "requires": { "tslib": "^2.3.0" } }, "@angular/forms": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.14.tgz", - "integrity": "sha512-fGrJ589tU+AKoxf+kaRrEw7wlSfVr1/z/Fz625ggFCc6ySQEityKW3JsnLfNkh5qGrdxib4BOfF78f9J7Pyk+w==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.25.tgz", + "integrity": "sha512-vGRo1LVPFo2Cu0k+QyDTlsBv5UbN0c3Et2YMS+43oyi1c4keocntBccOjLyM5C0kpMz4+pP81MqqYpAWu2k+TQ==", "requires": { "tslib": "^2.3.0" } }, "@angular/language-service": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-20.3.14.tgz", - "integrity": "sha512-3Jvi60WzLUe6jQJEw1xi/35uW7ynzxOS7iyZlwYfl2v8RljeLyyQsm0WNVpq6tXt80ppDeD59JvYTguEQ283Og==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-20.3.25.tgz", + "integrity": "sha512-3PZUwbDUVQXk9BLSiNUpDxTnRXGR3szwK9QFQaGDt8lhmLYaQLh3VJsY0FHDRghHZYIGR2P2MbVxGQHytF+IPw==", "dev": true }, "@angular/localize": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-20.3.14.tgz", - "integrity": "sha512-tSYZmFhjCHwifWE+R1VHg3zaemUX2tNjpQd9Ha6BvISyzyGWw/sQirIAxC4uoa2RVZ3jexos5sbw8rFT6iIEYg==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-20.3.25.tgz", + "integrity": "sha512-N4wmEBH44h58Av+1ivQ7LAe6Q0QP/2oBngvmnkO39AM6tadlnQGmaOvVzCUZe4BpOWYrrXQNiszSxGD3mUGgHg==", "requires": { "@babel/core": "7.28.3", "@types/babel__core": "7.20.5", "tinyglobby": "^0.2.12", "yargs": "^18.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==" - }, - "ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==" - }, - "cliui": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", - "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", - "requires": { - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - } - }, - "emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==" - }, - "string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "requires": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - } - }, - "strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "requires": { - "ansi-regex": "^6.0.1" - } - }, - "wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "requires": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - } - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" - }, - "yargs": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", - "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", - "requires": { - "cliui": "^9.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "string-width": "^7.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^22.0.0" - } - }, - "yargs-parser": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", - "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==" - } } }, "@angular/platform-browser": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.14.tgz", - "integrity": "sha512-Lviz9GfsIyOIBDal8QhIBKU8OMH29A0RhFw2opTC50sqKadXLN9CD7iSaAwQbNLc4mc3JAF4zth0AzKdHLbz7Q==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.25.tgz", + "integrity": "sha512-0k06U/AJRQifGMLkcU3R9uEHWbuKEzkKMuKcGagXTrkeFvCG2Ub4JdsbcjFNWB2bspWgaxIMSceuj7c83U5wOA==", "requires": { "tslib": "^2.3.0" } }, "@angular/platform-browser-dynamic": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.14.tgz", - "integrity": "sha512-g9z/g8gIOrBCX1SQ/GWwB0+JXBC6CKe0+yRyy9GGeBLm/YXWZHxTkmnDmueXXfPtUl8TOAInE22wlLcfunWTrg==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.25.tgz", + "integrity": "sha512-3Ku+IsN4tQPVBsw75SoLbLf7TsXAGL0rGPHSsyNYFhG2ZZeQuYNIAi8mc4cwz/qMDnuassHFrCxuLDgN6Yab5w==", "requires": { "tslib": "^2.3.0" } }, "@angular/platform-server": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-20.3.14.tgz", - "integrity": "sha512-CTc/K3AdOKjtU3PzK5cH8aRjpUZ2p7PVZ3JaVf9KMUHdRwNkPjMuRugoU4Adm5S3lGW4PsDuP49I6GkMsVDN6w==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-20.3.25.tgz", + "integrity": "sha512-uOpLILe5QP9WLXhwshA3fbHc+Wx/4nrUBZNns/dw3t06bRHMbbkutF8eVs+n6Atl332y4mOcStzMX1ilBUSFHw==", "requires": { "tslib": "^2.3.0", "xhr2": "^0.2.0" } }, "@angular/router": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.14.tgz", - "integrity": "sha512-gi7/NuHRS9n9RCwh03VuVFizVMa2lKL/s+7yP3Ecq2nQ5uSeTMWb/91OmGEBwncI3wKPkYdQ9g3n6PvK/O8uDQ==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.25.tgz", + "integrity": "sha512-YIjLHWAufTaukNj15hEoys29e7XNhnCRsS1/95h/OqR69R3adbB8hV7ut7gO6XdXokriYqb4gtoUjoESxR+xFQ==", "requires": { "tslib": "^2.3.0" } }, "@angular/ssr": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular/ssr/-/ssr-20.3.12.tgz", - "integrity": "sha512-liIxrlozOkcx+6qkMb0rFcKjo32aRtUI/U7TQ+1KA1XZrIk97aTjHEpq0KhBMjNlOt/xwrlUARDXjS5au5kP5g==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/ssr/-/ssr-20.3.25.tgz", + "integrity": "sha512-A/lbXQ+GucLAQfCJ5img7/xuqlftWjJU+iJABq/ARDkOOE/rNHrxFkXjJBntj97+oXjr7HBYI2sxYRZCCEiEag==", "requires": { "tslib": "^2.3.0" } }, "@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "requires": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } @@ -20314,22 +20310,22 @@ } }, "@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "requires": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" } }, "@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "requires": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" } }, "@babel/helper-optimise-call-expression": { @@ -20341,9 +20337,9 @@ } }, "@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==" + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==" }, "@babel/helper-remap-async-to-generator": { "version": "7.27.1", @@ -20417,11 +20413,11 @@ } }, "@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", "requires": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.29.0" } }, "@babel/plugin-bugfix-firefox-class-in-computed-class-key": { @@ -20721,14 +20717,14 @@ } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", - "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", + "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", "requires": { - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.5" + "@babel/traverse": "^7.29.0" } }, "@babel/plugin-transform-modules-umd": { @@ -21067,36 +21063,36 @@ "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==" }, "@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "requires": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" } }, "@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "requires": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "dependencies": { "@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "requires": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -21114,21 +21110,14 @@ } }, "@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "requires": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, - "@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "optional": true, - "peer": true - }, "@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -21139,9 +21128,9 @@ } }, "@cypress/request": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.9.tgz", - "integrity": "sha512-I3l7FdGRXluAS44/0NguwWlO83J18p0vlr2FYHrJkWdNYhgVoiYo61IXPqaOsL+vNxU1ZqMACzItGK3/KKDsdw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-4.0.1.tgz", + "integrity": "sha512-y20e+e6dFYkOUUJLVUZTsJRuTiXZaUQ32WD+R/ux/HBybbTx4ge7cNINcua0pU8+SNkKuRbOF12mBmzuzM8n5w==", "optional": true, "requires": { "aws-sign2": "~0.7.0", @@ -21157,17 +21146,16 @@ "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", - "qs": "6.14.0", + "qs": "^6.15.2", "safe-buffer": "^5.1.2", "tough-cookie": "^5.0.0", - "tunnel-agent": "^0.6.0", - "uuid": "^8.3.2" + "tunnel-agent": "^0.6.0" }, "dependencies": { "qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", "optional": true, "requires": { "side-channel": "^1.1.0" @@ -21229,159 +21217,159 @@ "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==" }, "@esbuild/aix-ppc64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", - "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", "optional": true }, "@esbuild/android-arm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", - "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", "optional": true }, "@esbuild/android-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", - "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", "optional": true }, "@esbuild/android-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", - "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", "optional": true }, "@esbuild/darwin-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", - "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", "optional": true }, "@esbuild/darwin-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", - "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", "optional": true }, "@esbuild/freebsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", - "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", "optional": true }, "@esbuild/freebsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", - "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", "optional": true }, "@esbuild/linux-arm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", - "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", "optional": true }, "@esbuild/linux-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", - "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", "optional": true }, "@esbuild/linux-ia32": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", - "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", "optional": true }, "@esbuild/linux-loong64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", - "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", "optional": true }, "@esbuild/linux-mips64el": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", - "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", "optional": true }, "@esbuild/linux-ppc64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", - "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", "optional": true }, "@esbuild/linux-riscv64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", - "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", "optional": true }, "@esbuild/linux-s390x": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", - "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", "optional": true }, "@esbuild/linux-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", - "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", "optional": true }, "@esbuild/netbsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", - "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", "optional": true }, "@esbuild/netbsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", - "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", "optional": true }, "@esbuild/openbsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", - "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", "optional": true }, "@esbuild/openbsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", - "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", "optional": true }, "@esbuild/openharmony-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", - "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", "optional": true }, "@esbuild/sunos-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", - "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", "optional": true }, "@esbuild/win32-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", - "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", "optional": true }, "@esbuild/win32-ia32": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", - "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", "optional": true }, "@esbuild/win32-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", - "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", "optional": true }, "@eslint-community/eslint-utils": { @@ -21562,6 +21550,12 @@ "@hapi/hoek": "^11.0.2" } }, + "@hono/node-server": { + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "requires": {} + }, "@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -21766,77 +21760,6 @@ "integrity": "sha512-QPaNt/nmE2bLGQa9b7wwyRJoLZ7pN6rcyXvzU0YCmivmJyq1BVo94G98tStRWkoD1RgDX5C+dPlhhHzNdu/W/w==", "requires": {} }, - "@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==" - }, - "@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", - "requires": { - "@isaacs/balanced-match": "^4.0.1" - } - }, - "@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "requires": { - "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" - }, - "dependencies": { - "ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==" - }, - "ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==" - }, - "emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" - }, - "string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "requires": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - } - }, - "strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "requires": { - "ansi-regex": "^6.0.1" - } - }, - "wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "requires": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - } - } - } - }, "@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -21905,9 +21828,9 @@ "requires": {} }, "@jsonjoy.com/buffers": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", - "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz", + "integrity": "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==", "requires": {} }, "@jsonjoy.com/codegen": { @@ -21916,6 +21839,131 @@ "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", "requires": {} }, + "@jsonjoy.com/fs-core": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.56.10.tgz", + "integrity": "sha512-PyAEA/3cnHhsGcdY+AmIU+ZPqTuZkDhCXQ2wkXypdLitSpd6d5Ivxhnq4wa2ETRWFVJGabYynBWxIijOswSmOw==", + "requires": { + "@jsonjoy.com/fs-node-builtins": "4.56.10", + "@jsonjoy.com/fs-node-utils": "4.56.10", + "thingies": "^2.5.0" + } + }, + "@jsonjoy.com/fs-fsa": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.56.10.tgz", + "integrity": "sha512-/FVK63ysNzTPOnCCcPoPHt77TOmachdMS422txM4KhxddLdbW1fIbFMYH0AM0ow/YchCyS5gqEjKLNyv71j/5Q==", + "requires": { + "@jsonjoy.com/fs-core": "4.56.10", + "@jsonjoy.com/fs-node-builtins": "4.56.10", + "@jsonjoy.com/fs-node-utils": "4.56.10", + "thingies": "^2.5.0" + } + }, + "@jsonjoy.com/fs-node": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.56.10.tgz", + "integrity": "sha512-7R4Gv3tkUdW3dXfXiOkqxkElxKNVdd8BDOWC0/dbERd0pXpPY+s2s1Mino+aTvkGrFPiY+mmVxA7zhskm4Ue4Q==", + "requires": { + "@jsonjoy.com/fs-core": "4.56.10", + "@jsonjoy.com/fs-node-builtins": "4.56.10", + "@jsonjoy.com/fs-node-utils": "4.56.10", + "@jsonjoy.com/fs-print": "4.56.10", + "@jsonjoy.com/fs-snapshot": "4.56.10", + "glob-to-regex.js": "^1.0.0", + "thingies": "^2.5.0" + } + }, + "@jsonjoy.com/fs-node-builtins": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.56.10.tgz", + "integrity": "sha512-uUnKz8R0YJyKq5jXpZtkGV9U0pJDt8hmYcLRrPjROheIfjMXsz82kXMgAA/qNg0wrZ1Kv+hrg7azqEZx6XZCVw==", + "requires": {} + }, + "@jsonjoy.com/fs-node-to-fsa": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.56.10.tgz", + "integrity": "sha512-oH+O6Y4lhn9NyG6aEoFwIBNKZeYy66toP5LJcDOMBgL99BKQMUf/zWJspdRhMdn/3hbzQsZ8EHHsuekbFLGUWw==", + "requires": { + "@jsonjoy.com/fs-fsa": "4.56.10", + "@jsonjoy.com/fs-node-builtins": "4.56.10", + "@jsonjoy.com/fs-node-utils": "4.56.10" + } + }, + "@jsonjoy.com/fs-node-utils": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.56.10.tgz", + "integrity": "sha512-8EuPBgVI2aDPwFdaNQeNpHsyqPi3rr+85tMNG/lHvQLiVjzoZsvxA//Xd8aB567LUhy4QS03ptT+unkD/DIsNg==", + "requires": { + "@jsonjoy.com/fs-node-builtins": "4.56.10" + } + }, + "@jsonjoy.com/fs-print": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.56.10.tgz", + "integrity": "sha512-JW4fp5mAYepzFsSGrQ48ep8FXxpg4niFWHdF78wDrFGof7F3tKDJln72QFDEn/27M1yHd4v7sKHHVPh78aWcEw==", + "requires": { + "@jsonjoy.com/fs-node-utils": "4.56.10", + "tree-dump": "^1.1.0" + } + }, + "@jsonjoy.com/fs-snapshot": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.56.10.tgz", + "integrity": "sha512-DkR6l5fj7+qj0+fVKm/OOXMGfDFCGXLfyHkORH3DF8hxkpDgIHbhf/DwncBMs2igu/ST7OEkexn1gIqoU6Y+9g==", + "requires": { + "@jsonjoy.com/buffers": "^17.65.0", + "@jsonjoy.com/fs-node-utils": "4.56.10", + "@jsonjoy.com/json-pack": "^17.65.0", + "@jsonjoy.com/util": "^17.65.0" + }, + "dependencies": { + "@jsonjoy.com/base64": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.67.0.tgz", + "integrity": "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==", + "requires": {} + }, + "@jsonjoy.com/codegen": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz", + "integrity": "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==", + "requires": {} + }, + "@jsonjoy.com/json-pack": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz", + "integrity": "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==", + "requires": { + "@jsonjoy.com/base64": "17.67.0", + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0", + "@jsonjoy.com/json-pointer": "17.67.0", + "@jsonjoy.com/util": "17.67.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + } + }, + "@jsonjoy.com/json-pointer": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz", + "integrity": "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==", + "requires": { + "@jsonjoy.com/util": "17.67.0" + } + }, + "@jsonjoy.com/util": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.67.0.tgz", + "integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==", + "requires": { + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0" + } + } + } + }, "@jsonjoy.com/json-pack": { "version": "1.21.0", "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", @@ -21929,6 +21977,14 @@ "hyperdyperid": "^1.2.0", "thingies": "^2.5.0", "tree-dump": "^1.1.0" + }, + "dependencies": { + "@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "requires": {} + } } }, "@jsonjoy.com/json-pointer": { @@ -21947,6 +22003,14 @@ "requires": { "@jsonjoy.com/buffers": "^1.0.0", "@jsonjoy.com/codegen": "^1.0.0" + }, + "dependencies": { + "@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "requires": {} + } } }, "@leichtgewicht/ip-codec": { @@ -21997,42 +22061,88 @@ "optional": true }, "@modelcontextprotocol/sdk": { - "version": "1.17.3", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.3.tgz", - "integrity": "sha512-JPwUKWSsbzx+DLFznf/QZ32Qa+ptfbUlHhRLrBQBAFu9iI1iYvizM4p+zhhRDceSsPutXp4z+R/HPVphlIiclg==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", + "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", "requires": { - "ajv": "^6.12.6", + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" }, "dependencies": { + "ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "requires": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + } + }, + "ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "requires": { + "ajv": "^8.0.0" + } + }, + "http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "requires": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + } + }, "iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "requires": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "raw-body": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", - "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.7.0", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" } + }, + "statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==" } } }, @@ -22208,11 +22318,16 @@ } }, "@ngtools/webpack": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-20.3.12.tgz", - "integrity": "sha512-ePuofHOtbgvEq2t+hcmL30s4q9HQ/nv9ABwpLiELdVIObcWUnrnizAvM7hujve/9CQL6gRCeEkxPLPS4ZrK9AQ==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-20.3.25.tgz", + "integrity": "sha512-p/YopAgukaIvezv3hsJzJevnNUBNTM8UQIkyDHPY5/aANdKraUTKDQUwlVxRiyD8U+PTSeD/BI56oOnuKrdFZQ==", "requires": {} }, + "@noble/secp256k1": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-3.1.0.tgz", + "integrity": "sha512-+F7iS7tUMaNGXcc9X3PjmjvuQnXEuSjCRNzVVA2xAcKXgCaP0dHYz4SFyt4FKNHef7sOP//xihowcySSS7PK9g==" + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -22237,61 +22352,59 @@ } }, "@npmcli/agent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", - "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", + "integrity": "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==", "requires": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", - "lru-cache": "^10.0.1", + "lru-cache": "^11.2.1", "socks-proxy-agent": "^8.0.3" - }, - "dependencies": { - "lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" - } } }, "@npmcli/fs": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", - "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-5.0.0.tgz", + "integrity": "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==", "requires": { "semver": "^7.3.5" } }, "@npmcli/git": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-6.0.3.tgz", - "integrity": "sha512-GUYESQlxZRAdhs3UhbB6pVRNUELQOHXwK9ruDkwmCv2aZ5y0SApQzUJCg02p3A7Ue2J5hxvlk1YI53c00NmRyQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-7.0.1.tgz", + "integrity": "sha512-+XTFxK2jJF/EJJ5SoAzXk3qwIDfvFc5/g+bD274LZ7uY7LE8sTfG6Z8rOanPl2ZEvZWqNvmEdtXC25cE54VcoA==", "requires": { - "@npmcli/promise-spawn": "^8.0.0", - "ini": "^5.0.0", - "lru-cache": "^10.0.1", - "npm-pick-manifest": "^10.0.0", - "proc-log": "^5.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "ini": "^6.0.0", + "lru-cache": "^11.2.1", + "npm-pick-manifest": "^11.0.1", + "proc-log": "^6.0.0", "promise-retry": "^2.0.1", "semver": "^7.3.5", - "which": "^5.0.0" + "which": "^6.0.0" }, "dependencies": { + "ini": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==" + }, "isexe": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==" }, - "lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + "proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==" }, "which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.0.tgz", + "integrity": "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==", "requires": { "isexe": "^3.1.1" } @@ -22299,59 +22412,51 @@ } }, "@npmcli/installed-package-contents": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-3.0.0.tgz", - "integrity": "sha512-fkxoPuFGvxyrH+OQzyTkX2LUEamrF4jZSmxjAtPPHHGO0dqsQ8tTKjnIS8SAnPHdk2I03BDtSMR5K/4loKg79Q==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-4.0.0.tgz", + "integrity": "sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA==", "requires": { - "npm-bundled": "^4.0.0", - "npm-normalize-package-bin": "^4.0.0" + "npm-bundled": "^5.0.0", + "npm-normalize-package-bin": "^5.0.0" } }, "@npmcli/node-gyp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-4.0.0.tgz", - "integrity": "sha512-+t5DZ6mO/QFh78PByMq1fGSAub/agLJZDRfJRMeOSNCt8s9YVlTjmGpIPwPhvXTGUIJk+WszlT0rQa1W33yzNA==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-5.0.0.tgz", + "integrity": "sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ==" }, "@npmcli/package-json": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-6.2.0.tgz", - "integrity": "sha512-rCNLSB/JzNvot0SEyXqWZ7tX2B5dD2a1br2Dp0vSYVo5jh8Z0EZ7lS9TsZ1UtziddB1UfNUaMCc538/HztnJGA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-7.0.4.tgz", + "integrity": "sha512-0wInJG3j/K40OJt/33ax47WfWMzZTm6OQxB9cDhTt5huCP2a9g2GnlsxmfN+PulItNPIpPrZ+kfwwUil7eHcZQ==", "requires": { - "@npmcli/git": "^6.0.0", - "glob": "^10.2.2", - "hosted-git-info": "^8.0.0", - "json-parse-even-better-errors": "^4.0.0", - "proc-log": "^5.0.0", + "@npmcli/git": "^7.0.0", + "glob": "^13.0.0", + "hosted-git-info": "^9.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", "semver": "^7.5.3", "validate-npm-package-license": "^3.0.4" }, "dependencies": { - "hosted-git-info": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", - "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", - "requires": { - "lru-cache": "^10.0.1" - } - }, "json-parse-even-better-errors": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", - "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-5.0.0.tgz", + "integrity": "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ==" }, - "lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + "proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==" } } }, "@npmcli/promise-spawn": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-8.0.3.tgz", - "integrity": "sha512-Yb00SWaL4F8w+K8YGhQ55+xE4RUNdMHV43WZGsiTM92gS+lC0mGsn7I4hLug7pbao035S6bj3Y3w0cUNGLfmkg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-9.0.1.tgz", + "integrity": "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==", "requires": { - "which": "^5.0.0" + "which": "^6.0.0" }, "dependencies": { "isexe": { @@ -22360,9 +22465,9 @@ "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==" }, "which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.0.tgz", + "integrity": "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==", "requires": { "isexe": "^3.1.1" } @@ -22370,21 +22475,21 @@ } }, "@npmcli/redact": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-3.2.2.tgz", - "integrity": "sha512-7VmYAmk4csGv08QzrDKScdzn11jHPFGyqJW39FyPgPuAp3zIaUmuCo1yxw9aGs+NEJuTGQ9Gwqpt93vtJubucg==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-4.0.0.tgz", + "integrity": "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==" }, "@npmcli/run-script": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-9.1.0.tgz", - "integrity": "sha512-aoNSbxtkePXUlbZB+anS1LqsJdctG5n3UVhfU47+CDdwMi6uNTBMF9gPcQRnqghQd2FGzcwwIFBruFMxjhBewg==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-10.0.3.tgz", + "integrity": "sha512-ER2N6itRkzWbbtVmZ9WKaWxVlKlOeBFF1/7xx+KA5J1xKa4JjUwBdb6tDpk0v1qA+d+VDwHI9qmLcXSWcmi+Rw==", "requires": { - "@npmcli/node-gyp": "^4.0.0", - "@npmcli/package-json": "^6.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "node-gyp": "^11.0.0", - "proc-log": "^5.0.0", - "which": "^5.0.0" + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "node-gyp": "^12.1.0", + "proc-log": "^6.0.0", + "which": "^6.0.0" }, "dependencies": { "isexe": { @@ -22392,10 +22497,15 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==" }, + "proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==" + }, "which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.0.tgz", + "integrity": "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==", "requires": { "isexe": "^3.1.1" } @@ -22519,12 +22629,6 @@ "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", "optional": true }, - "@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "optional": true - }, "@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -22532,164 +22636,182 @@ "peer": true }, "@rollup/rollup-android-arm-eabi": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.3.tgz", - "integrity": "sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", "optional": true }, "@rollup/rollup-android-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.3.tgz", - "integrity": "sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", "optional": true }, "@rollup/rollup-darwin-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.3.tgz", - "integrity": "sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", "optional": true }, "@rollup/rollup-darwin-x64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.3.tgz", - "integrity": "sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", "optional": true }, "@rollup/rollup-freebsd-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.3.tgz", - "integrity": "sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", "optional": true }, "@rollup/rollup-freebsd-x64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.3.tgz", - "integrity": "sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", "optional": true }, "@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.3.tgz", - "integrity": "sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", "optional": true }, "@rollup/rollup-linux-arm-musleabihf": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.3.tgz", - "integrity": "sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", "optional": true }, "@rollup/rollup-linux-arm64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.3.tgz", - "integrity": "sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", "optional": true }, "@rollup/rollup-linux-arm64-musl": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.3.tgz", - "integrity": "sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", "optional": true }, "@rollup/rollup-linux-loong64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.3.tgz", - "integrity": "sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "optional": true + }, + "@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", "optional": true }, "@rollup/rollup-linux-ppc64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.3.tgz", - "integrity": "sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "optional": true + }, + "@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", "optional": true }, "@rollup/rollup-linux-riscv64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.3.tgz", - "integrity": "sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", "optional": true }, "@rollup/rollup-linux-riscv64-musl": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.3.tgz", - "integrity": "sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", "optional": true }, "@rollup/rollup-linux-s390x-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.3.tgz", - "integrity": "sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", "optional": true }, "@rollup/rollup-linux-x64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.3.tgz", - "integrity": "sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", "optional": true }, "@rollup/rollup-linux-x64-musl": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.3.tgz", - "integrity": "sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "optional": true + }, + "@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", "optional": true }, "@rollup/rollup-openharmony-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.3.tgz", - "integrity": "sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", "optional": true }, "@rollup/rollup-win32-arm64-msvc": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.3.tgz", - "integrity": "sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", "optional": true }, "@rollup/rollup-win32-ia32-msvc": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.3.tgz", - "integrity": "sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", "optional": true }, "@rollup/rollup-win32-x64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.3.tgz", - "integrity": "sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", "optional": true }, "@rollup/rollup-win32-x64-msvc": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.3.tgz", - "integrity": "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", "optional": true }, "@schematics/angular": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.3.12.tgz", - "integrity": "sha512-ikl+nkWUab/Z4eSkBHgq9FLIUH8qh4OcYKeBQ0fyWqIUFHyjjK0JOfwmH1g/3zAmuUMtkthHCehAtyKzCTQjVA==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.3.25.tgz", + "integrity": "sha512-ezoJpPKhhjXgE3LxuNcJO/ghSXs8f73xrqGvqouag7IbuliJYbHrt22WH3X75XSbD0+iOdiqA2bARvAO/ezyxQ==", "requires": { - "@angular-devkit/core": "20.3.12", - "@angular-devkit/schematics": "20.3.12", + "@angular-devkit/core": "20.3.25", + "@angular-devkit/schematics": "20.3.25", "jsonc-parser": "3.3.1" }, "dependencies": { "@angular-devkit/core": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.12.tgz", - "integrity": "sha512-ReFxd/UOoVDr3+kIUjmYILQZF89qg62POdY7a7OqBH7plmInFlYVSEDouJvGqj3LVCPiqTk2ZOSChbhS/eLxXA==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.25.tgz", + "integrity": "sha512-pSfeWEoS1y9zxqTOw0Etj2NXkRmp/aU52wdnyq3KqVA9cJtgxtlOHlBBNswTil4dReojEy+0K1xJm+mLuWuLxA==", "requires": { - "ajv": "8.17.1", + "ajv": "8.18.0", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", - "picomatch": "4.0.3", + "picomatch": "4.0.4", "rxjs": "7.8.2", "source-map": "0.7.6" } }, "ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -22721,9 +22843,9 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==" + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, "readdirp": { "version": "4.1.2", @@ -22740,53 +22862,60 @@ } }, "@sigstore/bundle": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-3.1.0.tgz", - "integrity": "sha512-Mm1E3/CmDDCz3nDhFKTuYdB47EdRFRQMOE/EAbiG1MJW77/w1b3P7Qx7JSrVJs8PfwOLOVcKQCHErIwCTyPbag==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-4.0.0.tgz", + "integrity": "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==", "requires": { - "@sigstore/protobuf-specs": "^0.4.0" + "@sigstore/protobuf-specs": "^0.5.0" } }, "@sigstore/core": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-2.0.0.tgz", - "integrity": "sha512-nYxaSb/MtlSI+JWcwTHQxyNmWeWrUXJJ/G4liLrGG7+tS4vAz6LF3xRXqLH6wPIVUoZQel2Fs4ddLx4NCpiIYg==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-3.1.0.tgz", + "integrity": "sha512-o5cw1QYhNQ9IroioJxpzexmPjfCe7gzafd2RY3qnMpxr4ZEja+Jad/U8sgFpaue6bOaF+z7RVkyKVV44FN+N8A==" }, "@sigstore/protobuf-specs": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.4.3.tgz", - "integrity": "sha512-fk2zjD9117RL9BjqEwF7fwv7Q/P9yGsMV4MUJZ/DocaQJ6+3pKr+syBq1owU5Q5qGw5CUbXzm+4yJ2JVRDQeSA==" + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.5.0.tgz", + "integrity": "sha512-MM8XIwUjN2bwvCg1QvrMtbBmpcSHrkhFSCu1D11NyPvDQ25HEc4oG5/OcQfd/Tlf/OxmKWERDj0zGE23jQaMwA==" }, "@sigstore/sign": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-3.1.0.tgz", - "integrity": "sha512-knzjmaOHOov1Ur7N/z4B1oPqZ0QX5geUfhrVaqVlu+hl0EAoL4o+l0MSULINcD5GCWe3Z0+YJO8ues6vFlW0Yw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-4.1.0.tgz", + "integrity": "sha512-Vx1RmLxLGnSUqx/o5/VsCjkuN5L7y+vxEEwawvc7u+6WtX2W4GNa7b9HEjmcRWohw/d6BpATXmvOwc78m+Swdg==", "requires": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.4.0", - "make-fetch-happen": "^14.0.2", - "proc-log": "^5.0.0", + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0", + "make-fetch-happen": "^15.0.3", + "proc-log": "^6.1.0", "promise-retry": "^2.0.1" + }, + "dependencies": { + "proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==" + } } }, "@sigstore/tuf": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-3.1.1.tgz", - "integrity": "sha512-eFFvlcBIoGwVkkwmTi/vEQFSva3xs5Ot3WmBcjgjVdiaoelBLQaQ/ZBfhlG0MnG0cmTYScPpk7eDdGDWUcFUmg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-4.0.1.tgz", + "integrity": "sha512-OPZBg8y5Vc9yZjmWCHrlWPMBqW5yd8+wFNl+thMdtcWz3vjVSoJQutF8YkrzI0SLGnkuFof4HSsWUhXrf219Lw==", "requires": { - "@sigstore/protobuf-specs": "^0.4.1", - "tuf-js": "^3.0.1" + "@sigstore/protobuf-specs": "^0.5.0", + "tuf-js": "^4.1.0" } }, "@sigstore/verify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-2.1.1.tgz", - "integrity": "sha512-hVJD77oT67aowHxwT4+M6PGOp+E2LtLdTK3+FC0lBO9T7sYwItDMXZ7Z07IDCvR1M717a4axbIWckrW67KMP/w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-3.1.0.tgz", + "integrity": "sha512-mNe0Iigql08YupSOGv197YdHpPPr+EzDZmfCgMc7RPNaZTw5aLN01nBl6CHJOh3BGtnMIj83EeN4butBchc8Ag==", "requires": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.4.1" + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0" } }, "@sinonjs/commons": { @@ -22842,9 +22971,9 @@ "devOptional": true }, "@standard-schema/spec": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", - "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "optional": true }, "@tsconfig/node10": { @@ -22877,28 +23006,33 @@ "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==" }, "@tufjs/models": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-3.0.1.tgz", - "integrity": "sha512-UUYHISyhCU3ZgN8yaear3cGATHb3SMuKHsQ/nVbHXcmnBf+LzQ/cQfhNG+rfaSHgqGKNEm2cOCLVLELStUQ1JA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-4.1.0.tgz", + "integrity": "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww==", "requires": { "@tufjs/canonical-json": "2.0.0", - "minimatch": "^9.0.5" + "minimatch": "^10.1.1" }, "dependencies": { + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==" + }, "brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "requires": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" } }, "minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.3.tgz", + "integrity": "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==", "requires": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" } } } @@ -23167,15 +23301,6 @@ "@types/node": "*" } }, - "@types/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", - "optional": true, - "requires": { - "@types/node": "*" - } - }, "@typescript-eslint/eslint-plugin": { "version": "8.46.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz", @@ -23271,22 +23396,28 @@ "ts-api-utils": "^2.1.0" }, "dependencies": { + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true + }, "brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "requires": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" } }, "minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz", + "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==", "dev": true, "requires": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" } } } @@ -23474,9 +23605,9 @@ "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==" }, "abbrev": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", - "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==" }, "accepts": { "version": "1.3.8", @@ -23513,20 +23644,11 @@ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==" }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "optional": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -23543,9 +23665,9 @@ }, "dependencies": { "ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -23587,12 +23709,11 @@ "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==" }, "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "optional": true, + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", "requires": { - "type-fest": "^0.21.3" + "environment": "^1.0.0" } }, "ansi-html-community": { @@ -23665,12 +23786,6 @@ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "optional": true }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "optional": true - }, "async-each-series": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/async-each-series/-/async-each-series-0.1.1.tgz", @@ -23715,20 +23830,20 @@ "optional": true }, "axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", + "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", "optional": true, "requires": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" }, "dependencies": { "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", "optional": true } } @@ -23805,7 +23920,8 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "devOptional": true }, "base64-js": { "version": "1.5.1", @@ -23820,9 +23936,9 @@ "devOptional": true }, "baseline-browser-mapping": { - "version": "2.8.22", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.22.tgz", - "integrity": "sha512-/tk9kky/d8T8CTXIQYASLyhAxR5VwL3zct1oAoVTaOUHwrmsGnfbRwNdEq+vOl2BN8i3PcDdP0o4Q+jjKQoFbQ==" + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==" }, "batch": { "version": "0.6.1", @@ -23876,22 +23992,22 @@ "optional": true }, "body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", "requires": { - "bytes": "3.1.2", + "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", + "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" + "unpipe": "~1.0.0" }, "dependencies": { "debug": { @@ -23902,10 +24018,22 @@ "ms": "2.0.0" } }, + "http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "requires": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "on-finished": { "version": "2.4.1", @@ -23914,6 +24042,11 @@ "requires": { "ee-first": "1.1.1" } + }, + "statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==" } } }, @@ -23932,15 +24065,15 @@ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, "bootstrap": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz", - "integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==", + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz", + "integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==", "requires": {} }, "brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "devOptional": true, "requires": { "balanced-match": "^1.0.0", @@ -24101,15 +24234,15 @@ } }, "browserslist": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", - "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "requires": { - "baseline-browser-mapping": "^2.8.19", - "caniuse-lite": "^1.0.30001751", - "electron-to-chromium": "^1.5.238", - "node-releases": "^2.0.26", - "update-browserslist-db": "^1.1.4" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" } }, "bs-recipes": { @@ -24128,12 +24261,6 @@ "ieee754": "^1.1.13" } }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "optional": true - }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -24153,62 +24280,34 @@ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, "cacache": { - "version": "19.0.1", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", - "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.3.tgz", + "integrity": "sha512-3pUp4e8hv07k1QlijZu6Kn7c9+ZpWWk4j3F8N3xPuCExULobqJydKYOTj1FTq58srkJsXvO7LbGAH4C0ZU3WGw==", "requires": { - "@npmcli/fs": "^4.0.0", + "@npmcli/fs": "^5.0.0", "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^10.0.1", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", - "ssri": "^12.0.0", - "tar": "^7.4.3", - "unique-filename": "^4.0.0" + "ssri": "^13.0.0", + "unique-filename": "^5.0.0" }, "dependencies": { - "chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==" - }, - "lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" - }, "p-map": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", - "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==" - }, - "tar": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", - "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", - "requires": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - } - }, - "yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==" + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==" } } }, "cachedir": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz", - "integrity": "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", + "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", "optional": true }, "call-bind-apply-helpers": { @@ -24240,9 +24339,9 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, "caniuse-lite": { - "version": "1.0.30001752", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001752.tgz", - "integrity": "sha512-vKUk7beoukxE47P5gcVNKkDRzXdVofotshHwfR9vmpeFKxmI5PBpgOMC18LUJUA/DvJ70Y7RveasIBraqsyO/g==" + "version": "1.0.30001770", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz", + "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==" }, "caseless": { "version": "0.12.0", @@ -24319,9 +24418,9 @@ } }, "chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==" }, "chrome-trace-event": { "version": "1.0.3", @@ -24334,19 +24433,12 @@ "integrity": "sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==", "optional": true }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "optional": true - }, "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "optional": true, + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "requires": { - "restore-cursor": "^3.1.0" + "restore-cursor": "^5.0.0" } }, "cli-spinners": { @@ -24365,13 +24457,40 @@ } }, "cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", "optional": true, "requires": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "optional": true + }, + "string-width": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", + "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", + "optional": true, + "requires": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + } + }, + "strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "optional": true, + "requires": { + "ansi-regex": "^6.2.2" + } + } } }, "cli-width": { @@ -24492,11 +24611,6 @@ "version": "0.6.4", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==" - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" } } }, @@ -24563,19 +24677,9 @@ "devOptional": true }, "content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", - "requires": { - "safe-buffer": "5.2.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - } - } + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==" }, "content-type": { "version": "1.0.5", @@ -24606,14 +24710,14 @@ } }, "copy-webpack-plugin": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-13.0.1.tgz", - "integrity": "sha512-J+YV3WfhY6W/Xf9h+J1znYuqTye2xkBUIGyTPWuBAT27qajBa5mR4f8WBmfDY3YjRftT2kqZZiLi1qf0H+UOFw==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-14.0.0.tgz", + "integrity": "sha512-3JLW90aBGeaTLpM7mYQKpnVdgsUZRExY55giiZgLuX/xTQRUs1dOCwbBnWnvY6Q6rfZoXMNwzOQJCSZPppfqXA==", "requires": { "glob-parent": "^6.0.1", "normalize-path": "^3.0.0", "schema-utils": "^4.2.0", - "serialize-javascript": "^6.0.2", + "serialize-javascript": "^7.0.3", "tinyglobby": "^0.2.12" }, "dependencies": { @@ -24713,20 +24817,13 @@ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" }, - "custom-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", - "optional": true, - "peer": true - }, "cypress": { - "version": "15.7.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-15.7.0.tgz", - "integrity": "sha512-1C81zKxnQckYm2XGi37rPV4rN0bzUoWhydhKdOyshJn5gJKszEx5as9VLSZI0jp0ye49QxmnbU4TtMpcD+OmGQ==", + "version": "15.16.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-15.16.0.tgz", + "integrity": "sha512-fy0M0c9xDLEp4v9y7LLKFeAQhIdDsobxDSKpD3JcZpqQefjy9TSzEyVV3HA0zu7hUi0bGHlSYlI7ASub8wgR9A==", "optional": true, "requires": { - "@cypress/request": "^3.0.9", + "@cypress/request": "^4.0.0", "@cypress/xvfb": "^1.2.4", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", @@ -24735,26 +24832,22 @@ "blob-util": "^2.0.2", "bluebird": "^3.7.2", "buffer": "^5.7.1", - "cachedir": "^2.3.0", + "cachedir": "^2.4.0", "chalk": "^4.1.0", "ci-info": "^4.1.0", - "cli-cursor": "^3.1.0", "cli-table3": "0.6.1", "commander": "^6.2.1", "common-tags": "^1.8.0", "dayjs": "^1.10.4", "debug": "^4.3.4", - "enquirer": "^2.3.6", "eventemitter2": "6.4.7", "execa": "4.1.0", "executable": "^4.1.1", - "extract-zip": "2.0.1", - "figures": "^3.2.0", "fs-extra": "^9.1.0", "hasha": "5.2.2", "is-installed-globally": "~0.4.0", - "listr2": "^3.8.3", - "lodash": "^4.17.21", + "listr2": "^9.0.5", + "lodash": "^4.17.23", "log-symbols": "^4.0.0", "minimist": "^1.2.8", "ospath": "^1.2.2", @@ -24763,11 +24856,12 @@ "proxy-from-env": "1.0.0", "request-progress": "^3.0.0", "supports-color": "^8.1.1", - "systeminformation": "5.27.7", + "systeminformation": "^5.31.1", "tmp": "~0.2.4", "tree-kill": "1.2.2", + "tslib": "1.14.1", "untildify": "^4.0.0", - "yauzl": "^2.10.0" + "yauzl": "^3.3.1" }, "dependencies": { "commander": { @@ -24828,6 +24922,12 @@ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", "optional": true + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "optional": true } } }, @@ -24858,13 +24958,6 @@ "assert-plus": "^1.0.0" } }, - "date-format": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.3.tgz", - "integrity": "sha512-7P3FyqDcfeznLZp2b+OMitV9Sz2lUnsT87WaTat9nVwqsBkTzPG3lPLNwW3en6F4pHUiWzr6vb8CLhjdK9bcxQ==", - "optional": true, - "peer": true - }, "dayjs": { "version": "1.11.9", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", @@ -24900,18 +24993,18 @@ "dev": true }, "default-browser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", - "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", "requires": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "default-browser-id": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", - "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==" }, "define-lazy-prop": { "version": "3.0.0", @@ -24956,17 +25049,10 @@ "integrity": "sha512-LmVkry/oDShEgSZPNgqCIp2/TlqtExeGmymru3uCELnfyjY11IzpAproLYs+1X88fXO6DBoYP3ul2Xo2yz2j6A==", "devOptional": true }, - "di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", - "optional": true, - "peer": true - }, "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", "dev": true }, "dijkstrajs": { @@ -24982,19 +25068,6 @@ "@leichtgewicht/ip-codec": "^2.0.1" } }, - "dom-serialize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", - "optional": true, - "peer": true, - "requires": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" - } - }, "dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -25049,11 +25122,6 @@ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "optional": true }, - "eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" - }, "easy-extender": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/easy-extender/-/easy-extender-2.3.4.tgz", @@ -25083,12 +25151,12 @@ } }, "echarts": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.4.3.tgz", - "integrity": "sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-6.1.0.tgz", + "integrity": "sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==", "requires": { "tslib": "2.3.0", - "zrender": "5.4.4" + "zrender": "6.1.0" }, "dependencies": { "tslib": { @@ -25104,9 +25172,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "electron-to-chromium": { - "version": "1.5.244", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.244.tgz", - "integrity": "sha512-OszpBN7xZX4vWMPJwB9illkN/znA8M36GQqQxi6MNy9axWxhOfJyZZJtSLQCpEFLHP2xK33BiWx9aIuIEXVCcw==" + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==" }, "emoji-regex": { "version": "8.0.0", @@ -25195,31 +25263,14 @@ "devOptional": true }, "enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", "requires": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.0" } }, - "enquirer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", - "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", - "optional": true, - "requires": { - "ansi-colors": "^4.1.1", - "strip-ansi": "^6.0.1" - } - }, - "ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", - "optional": true, - "peer": true - }, "entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -25268,9 +25319,9 @@ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" }, "es-module-lexer": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz", - "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==" }, "es-object-atoms": { "version": "1.1.1", @@ -25293,42 +25344,42 @@ } }, "esbuild": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", - "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", "requires": { - "@esbuild/aix-ppc64": "0.25.9", - "@esbuild/android-arm": "0.25.9", - "@esbuild/android-arm64": "0.25.9", - "@esbuild/android-x64": "0.25.9", - "@esbuild/darwin-arm64": "0.25.9", - "@esbuild/darwin-x64": "0.25.9", - "@esbuild/freebsd-arm64": "0.25.9", - "@esbuild/freebsd-x64": "0.25.9", - "@esbuild/linux-arm": "0.25.9", - "@esbuild/linux-arm64": "0.25.9", - "@esbuild/linux-ia32": "0.25.9", - "@esbuild/linux-loong64": "0.25.9", - "@esbuild/linux-mips64el": "0.25.9", - "@esbuild/linux-ppc64": "0.25.9", - "@esbuild/linux-riscv64": "0.25.9", - "@esbuild/linux-s390x": "0.25.9", - "@esbuild/linux-x64": "0.25.9", - "@esbuild/netbsd-arm64": "0.25.9", - "@esbuild/netbsd-x64": "0.25.9", - "@esbuild/openbsd-arm64": "0.25.9", - "@esbuild/openbsd-x64": "0.25.9", - "@esbuild/openharmony-arm64": "0.25.9", - "@esbuild/sunos-x64": "0.25.9", - "@esbuild/win32-arm64": "0.25.9", - "@esbuild/win32-ia32": "0.25.9", - "@esbuild/win32-x64": "0.25.9" + "@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" } }, "esbuild-wasm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.25.9.tgz", - "integrity": "sha512-Jpv5tCSwQg18aCqCRD3oHIX/prBhXMDapIoG//A+6+dV0e7KQMGFg85ihJ5T1EeMjbZjON3TqFy0VrGAnIHLDA==" + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.28.0.tgz", + "integrity": "sha512-5TRVKExcEmeMkccIZMzUq+Az6X2RoMAJyfl6SMMO1dMVhmvt0I2mx7gAb6zYi42n4d1ETcatFXazGKzA+aW7fg==" }, "escalade": { "version": "3.2.0", @@ -25340,12 +25391,6 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "optional": true - }, "eslint": { "version": "9.39.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.0.tgz", @@ -25637,17 +25682,18 @@ "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==" }, "express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "requires": { "accepts": "^2.0.0", - "body-parser": "^2.2.0", + "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", + "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", @@ -25680,9 +25726,9 @@ } }, "body-parser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", - "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "requires": { "bytes": "^3.1.2", "content-type": "^1.0.5", @@ -25690,7 +25736,7 @@ "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", - "qs": "^6.14.0", + "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } @@ -25713,10 +25759,22 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==" }, + "http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "requires": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + } + }, "iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "requires": { "safer-buffer": ">= 2.1.2 < 3.0.0" } @@ -25732,9 +25790,9 @@ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==" }, "mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "requires": { "mime-db": "^1.54.0" } @@ -25757,47 +25815,39 @@ "ee-first": "1.1.1" } }, - "qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "requires": { - "side-channel": "^1.1.0" - } - }, "raw-body": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", - "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.7.0", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" } }, "send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "requires": { - "debug": "^4.3.5", + "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", - "statuses": "^2.0.1" + "statuses": "^2.0.2" } }, "serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "requires": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", @@ -25823,10 +25873,12 @@ } }, "express-rate-limit": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", - "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", - "requires": {} + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz", + "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==", + "requires": { + "ip-address": "10.1.0" + } }, "extend": { "version": "3.0.2", @@ -25834,29 +25886,6 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "optional": true }, - "extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "optional": true, - "requires": { - "@types/yauzl": "^2.9.1", - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "optional": true, - "requires": { - "pump": "^3.0.0" - } - } - } - }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -25883,7 +25912,8 @@ "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true }, "fast-levenshtein": { "version": "2.0.6", @@ -25912,24 +25942,6 @@ "websocket-driver": ">=0.5.1" } }, - "fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "optional": true, - "requires": { - "pend": "~1.2.0" - } - }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "optional": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, "file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -25948,9 +25960,9 @@ } }, "finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "requires": { "debug": "^4.4.0", "encodeurl": "^2.0.0", @@ -26018,31 +26030,15 @@ } }, "flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "devOptional": true + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true }, "follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==" - }, - "foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "requires": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "dependencies": { - "signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" - } - } + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==" }, "forever-agent": { "version": "0.6.1", @@ -26051,9 +26047,9 @@ "optional": true }, "form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "optional": true, "requires": { "asynckit": "^0.4.0", @@ -26084,18 +26080,6 @@ "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", "optional": true }, - "fs-extra": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", - "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", - "optional": true, - "peer": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, "fs-minipass": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", @@ -26104,13 +26088,6 @@ "minipass": "^7.0.3" } }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "optional": true, - "peer": true - }, "fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -26133,9 +26110,9 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-east-asian-width": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", - "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==" }, "get-func-name": { "version": "2.0.2", @@ -26185,32 +26162,34 @@ } }, "glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz", + "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==", "requires": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", + "minimatch": "^10.1.1", "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "path-scurry": "^2.0.0" }, "dependencies": { + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==" + }, "brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "requires": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" } }, "minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.3.tgz", + "integrity": "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==", "requires": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" } } } @@ -26331,19 +26310,17 @@ "function-bind": "^1.1.2" } }, + "hono": { + "version": "4.12.25", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.25.tgz", + "integrity": "sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==" + }, "hosted-git-info": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", "requires": { "lru-cache": "^11.1.0" - }, - "dependencies": { - "lru-cache": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", - "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==" - } } }, "hpack.js": { @@ -26355,23 +26332,52 @@ "obuf": "^1.0.0", "readable-stream": "^2.0.1", "wbuf": "^1.1.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } } }, "htmlparser2": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", - "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", "requires": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", - "domutils": "^3.2.1", - "entities": "^6.0.0" + "domutils": "^3.2.2", + "entities": "^7.0.1" }, "dependencies": { "entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==" + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==" } } }, @@ -26505,12 +26511,25 @@ "minimatch": "^10.0.3" }, "dependencies": { - "minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==" + }, + "brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "requires": { - "@isaacs/brace-expansion": "^5.0.0" + "balanced-match": "^4.0.2" + } + }, + "minimatch": { + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.3.tgz", + "integrity": "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==", + "requires": { + "brace-expansion": "^5.0.2" } } } @@ -26522,9 +26541,9 @@ "optional": true }, "immutable": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz", - "integrity": "sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.3.tgz", + "integrity": "sha512-AUY/VyX0E5XlibOmWt10uabJzam1zlYjwiEgQSDc5+UIkFNaF9WM0JxXKaNMGf+F/ffUF+7kRKXM9A7C0xXqMg==", "devOptional": true }, "import-fresh": { @@ -26548,23 +26567,6 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "optional": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "optional": true, - "peer": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -26576,9 +26578,9 @@ "integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==" }, "ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==" + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==" }, "ipaddr.js": { "version": "1.9.1", @@ -26719,19 +26721,17 @@ "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==" }, "is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", "requires": { "is-inside-container": "^1.0.0" } }, - "isbinaryfile": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.8.tgz", - "integrity": "sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w==", - "optional": true, - "peer": true + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "isexe": { "version": "2.0.0", @@ -26766,15 +26766,6 @@ "semver": "^7.5.4" } }, - "jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "requires": { - "@isaacs/cliui": "^8.0.2", - "@pkgjs/parseargs": "^0.11.0" - } - }, "jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", @@ -26801,9 +26792,9 @@ "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==" }, "joi": { - "version": "18.0.1", - "resolved": "https://registry.npmjs.org/joi/-/joi-18.0.1.tgz", - "integrity": "sha512-IiQpRyypSnLisQf3PwuN2eIHAsAIGZIrLZkd4zdvIar2bDyhM91ubRjy8a3eYablXsh9BeI/c7dmPYHca5qtoA==", + "version": "18.2.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-18.2.1.tgz", + "integrity": "sha512-2/OKlogiESf2Nh3TFCrRjrr9z1DRHeW0I+KReF67+4J0Ns+8hBtHRmoWAZ2OFU6I5+TWLEe6sVlSdXPjHm5UbQ==", "optional": true, "requires": { "@hapi/address": "^5.1.1", @@ -26812,14 +26803,13 @@ "@hapi/pinpoint": "^2.0.1", "@hapi/tlds": "^1.1.1", "@hapi/topo": "^6.0.2", - "@standard-schema/spec": "^1.0.0" + "@standard-schema/spec": "^1.1.0" } }, - "jquery": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", - "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==", - "peer": true + "jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==" }, "js-tokens": { "version": "4.0.0", @@ -26827,9 +26817,9 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", "requires": { "argparse": "^2.0.1" } @@ -26865,7 +26855,13 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -26902,7 +26898,7 @@ "jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==" }, "jsprim": { "version": "2.0.2", @@ -26922,123 +26918,6 @@ "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", "optional": true }, - "karma": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", - "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", - "optional": true, - "peer": true, - "requires": { - "@colors/colors": "1.5.0", - "body-parser": "^1.19.0", - "braces": "^3.0.2", - "chokidar": "^3.5.1", - "connect": "^3.7.0", - "di": "^0.0.1", - "dom-serialize": "^2.2.1", - "glob": "^7.1.7", - "graceful-fs": "^4.2.6", - "http-proxy": "^1.18.1", - "isbinaryfile": "^4.0.8", - "lodash": "^4.17.21", - "log4js": "^6.4.1", - "mime": "^2.5.2", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.5", - "qjobs": "^1.2.0", - "range-parser": "^1.2.1", - "rimraf": "^3.0.2", - "socket.io": "^4.7.2", - "source-map": "^0.6.1", - "tmp": "^0.2.1", - "ua-parser-js": "^0.7.30", - "yargs": "^16.1.1" - }, - "dependencies": { - "connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "optional": true, - "peer": true, - "requires": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "optional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "optional": true, - "peer": true, - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "optional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "optional": true, - "peer": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "optional": true, - "peer": true - }, - "tmp": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", - "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", - "optional": true, - "peer": true - } - } - }, "karma-source-map-support": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", @@ -27062,12 +26941,12 @@ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, "launch-editor": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.12.0.tgz", - "integrity": "sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.14.1.tgz", + "integrity": "sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==", "requires": { "picocolors": "^1.1.1", - "shell-quote": "^1.8.3" + "shell-quote": "^1.8.4" } }, "lazy-ass": { @@ -27137,30 +27016,72 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "listr2": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", - "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", "optional": true, "requires": { - "cli-truncate": "^2.1.0", - "colorette": "^2.0.16", - "log-update": "^4.0.0", - "p-map": "^4.0.0", - "rfdc": "^1.3.0", - "rxjs": "^7.5.1", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" + "cli-truncate": "^5.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" }, "dependencies": { - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "optional": true + }, + "ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "optional": true + }, + "emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "optional": true + }, + "eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "optional": true + }, + "string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "optional": true, "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + } + }, + "strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "optional": true, + "requires": { + "ansi-regex": "^6.2.2" + } + }, + "wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "optional": true, + "requires": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" } } } @@ -27186,9 +27107,9 @@ } }, "loader-runner": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", - "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==" + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==" }, "loader-utils": { "version": "2.0.4", @@ -27209,9 +27130,9 @@ } }, "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "devOptional": true }, "lodash.debounce": { @@ -27254,44 +27175,79 @@ } }, "log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", - "optional": true, + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", "requires": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" }, "dependencies": { - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "optional": true, + "ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==" + }, + "ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==" + }, + "emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==" + }, + "is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" + "get-east-asian-width": "^1.3.1" + } + }, + "slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "requires": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + } + }, + "string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "requires": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + } + }, + "strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "requires": { + "ansi-regex": "^6.2.2" + } + }, + "wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "requires": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" } } } }, - "log4js": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.4.1.tgz", - "integrity": "sha512-iUiYnXqAmNKiIZ1XSAitQ4TmNs8CdZYTAWINARF3LjnsLN8tY5m0vRwd6uuWj/yNY0YHxeZodnbmxKFUOM2rMg==", - "optional": true, - "peer": true, - "requires": { - "date-format": "^4.0.3", - "debug": "^4.3.3", - "flatted": "^3.2.4", - "rfdc": "^1.3.0", - "streamroller": "^3.0.2" - } - }, "loupe": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", @@ -27301,6 +27257,11 @@ "get-func-name": "^2.0.1" } }, + "lru-cache": { + "version": "11.2.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", + "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==" + }, "magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", @@ -27340,27 +27301,32 @@ "dev": true }, "make-fetch-happen": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", - "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.3.tgz", + "integrity": "sha512-iyyEpDty1mwW3dGlYXAJqC/azFn5PPvgKVwXayOGBSmKLxhKZ9fg4qIan2ePpp1vJIwfFiO34LAPZgq9SZW9Aw==", "requires": { - "@npmcli/agent": "^3.0.0", - "cacache": "^19.0.1", + "@npmcli/agent": "^4.0.0", + "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", - "minipass-fetch": "^4.0.0", + "minipass-fetch": "^5.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", - "proc-log": "^5.0.0", + "proc-log": "^6.0.0", "promise-retry": "^2.0.1", - "ssri": "^12.0.0" + "ssri": "^13.0.0" }, "dependencies": { "negotiator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==" + }, + "proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==" } } }, @@ -27378,13 +27344,21 @@ "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" }, "memfs": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.50.0.tgz", - "integrity": "sha512-N0LUYQMUA1yS5tJKmMtU9yprPm6ZIg24yr/OVv/7t6q0kKDIho4cBbXRi1XKttUmNYDYgF/q45qrKE/UhGO0CA==", + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.56.10.tgz", + "integrity": "sha512-eLvzyrwqLHnLYalJP7YZ3wBe79MXktMdfQbvMrVD80K+NhrIukCVBvgP30zTJYEEDh9hZ/ep9z0KOdD7FSHo7w==", "requires": { + "@jsonjoy.com/fs-core": "4.56.10", + "@jsonjoy.com/fs-fsa": "4.56.10", + "@jsonjoy.com/fs-node": "4.56.10", + "@jsonjoy.com/fs-node-builtins": "4.56.10", + "@jsonjoy.com/fs-node-to-fsa": "4.56.10", + "@jsonjoy.com/fs-node-utils": "4.56.10", + "@jsonjoy.com/fs-print": "4.56.10", + "@jsonjoy.com/fs-snapshot": "4.56.10", "@jsonjoy.com/json-pack": "^1.11.0", "@jsonjoy.com/util": "^1.9.0", "glob-to-regex.js": "^1.0.1", @@ -27422,13 +27396,6 @@ "picomatch": "^2.3.1" } }, - "mime": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", - "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", - "optional": true, - "peer": true - }, "mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -27468,9 +27435,9 @@ "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.4.tgz", + "integrity": "sha512-twmL+S8+7yIsE9wsqgzU3E8/LumN3M3QELrBZ20OdmQ9jB2JvW5oZtBEmft84k/Gs5CG9mqtWc6Y9vW+JEzGxw==", "devOptional": true, "requires": { "brace-expansion": "^1.1.7" @@ -27496,9 +27463,9 @@ } }, "minipass-fetch": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", - "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.0.tgz", + "integrity": "sha512-fiCdUALipqgPWrOVTz9fw0XhcazULXOSU6ie40DDbX1F49p1dBrSRBuswndTx1x3vEb/g0FT7vC4c4C2u/mh3A==", "requires": { "encoding": "^0.1.13", "minipass": "^7.0.3", @@ -27521,6 +27488,11 @@ "requires": { "yallist": "^4.0.0" } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } }, @@ -27539,6 +27511,11 @@ "requires": { "yallist": "^4.0.0" } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } }, @@ -27557,6 +27534,11 @@ "requires": { "yallist": "^4.0.0" } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } }, @@ -27574,16 +27556,6 @@ "integrity": "sha512-r6lj77KlwqLhIUku9UWYes7KJtsczvolZkzp8hbaDPPaE24OmWl5s539Mytlj22siEQKosZ26qCBgda2PKwoJw==", "devOptional": true }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "optional": true, - "peer": true, - "requires": { - "minimist": "^1.2.5" - } - }, "mock-socket": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz", @@ -27601,9 +27573,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "msgpackr": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.5.tgz", - "integrity": "sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==", + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.8.tgz", + "integrity": "sha512-bC4UGzHhVvgDNS7kn9tV8fAucIYUBuGojcaLiz7v+P63Lmtm0Xeji8B/8tYKddALXxJLpwIeBmUN3u64C4YkRA==", "optional": true, "requires": { "msgpackr-extract": "^3.0.2" @@ -27762,61 +27734,44 @@ "optional": true }, "node-forge": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz", - "integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==" }, "node-gyp": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz", - "integrity": "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==", + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.2.0.tgz", + "integrity": "sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ==", "requires": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^14.0.3", - "nopt": "^8.0.0", - "proc-log": "^5.0.0", + "make-fetch-happen": "^15.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.5", - "tar": "^7.4.3", + "tar": "^7.5.5", "tinyglobby": "^0.2.12", - "which": "^5.0.0" + "which": "^6.0.0" }, "dependencies": { - "chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==" - }, "isexe": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==" }, - "tar": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", - "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", - "requires": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - } + "proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==" }, "which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.0.tgz", + "integrity": "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==", "requires": { "isexe": "^3.1.1" } - }, - "yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==" } } }, @@ -27835,11 +27790,11 @@ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==" }, "nopt": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", - "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", "requires": { - "abbrev": "^3.0.0" + "abbrev": "^4.0.0" } }, "normalize-path": { @@ -27853,25 +27808,25 @@ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==" }, "npm-bundled": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-4.0.0.tgz", - "integrity": "sha512-IxaQZDMsqfQ2Lz37VvyyEtKLe8FsRZuysmedy/N06TU1RyVppYKXrO4xIhR0F+7ubIBox6Q7nir6fQI3ej39iA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-5.0.0.tgz", + "integrity": "sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw==", "requires": { - "npm-normalize-package-bin": "^4.0.0" + "npm-normalize-package-bin": "^5.0.0" } }, "npm-install-checks": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-7.1.2.tgz", - "integrity": "sha512-z9HJBCYw9Zr8BqXcllKIs5nI+QggAImbBdHphOzVYrz2CB4iQ6FzWyKmlqDZua+51nAu7FcemlbTc9VgQN5XDQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-8.0.0.tgz", + "integrity": "sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA==", "requires": { "semver": "^7.1.1" } }, "npm-normalize-package-bin": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", - "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-5.0.0.tgz", + "integrity": "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==" }, "npm-package-arg": { "version": "13.0.0", @@ -27894,87 +27849,42 @@ }, "dependencies": { "proc-log": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.0.0.tgz", - "integrity": "sha512-KG/XsTDN901PNfPfAMmj6N/Ywg9tM+bHK8pAz+27fS4N4Pcr+4zoYBOcGSBu6ceXYNPxkLpa4ohtfxV1XcLAfA==" + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==" } } }, "npm-pick-manifest": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-10.0.0.tgz", - "integrity": "sha512-r4fFa4FqYY8xaM7fHecQ9Z2nE9hgNfJR+EmoKv0+chvzWkBcORX3r0FpTByP+CbOVJDladMXnPQGVN8PBLGuTQ==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-11.0.3.tgz", + "integrity": "sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ==", "requires": { - "npm-install-checks": "^7.1.0", - "npm-normalize-package-bin": "^4.0.0", - "npm-package-arg": "^12.0.0", + "npm-install-checks": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "npm-package-arg": "^13.0.0", "semver": "^7.3.5" - }, - "dependencies": { - "hosted-git-info": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", - "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", - "requires": { - "lru-cache": "^10.0.1" - } - }, - "lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" - }, - "npm-package-arg": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", - "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", - "requires": { - "hosted-git-info": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^6.0.0" - } - } } }, "npm-registry-fetch": { - "version": "18.0.2", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-18.0.2.tgz", - "integrity": "sha512-LeVMZBBVy+oQb5R6FDV9OlJCcWDU+al10oKpe+nsvcHnG24Z3uM3SvJYKfGJlfGjVU8v9liejCrUR/M5HO5NEQ==", + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-19.1.1.tgz", + "integrity": "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw==", "requires": { - "@npmcli/redact": "^3.0.0", + "@npmcli/redact": "^4.0.0", "jsonparse": "^1.3.1", - "make-fetch-happen": "^14.0.0", + "make-fetch-happen": "^15.0.0", "minipass": "^7.0.2", - "minipass-fetch": "^4.0.0", + "minipass-fetch": "^5.0.0", "minizlib": "^3.0.1", - "npm-package-arg": "^12.0.0", - "proc-log": "^5.0.0" + "npm-package-arg": "^13.0.0", + "proc-log": "^6.0.0" }, "dependencies": { - "hosted-git-info": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", - "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", - "requires": { - "lru-cache": "^10.0.1" - } - }, - "lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" - }, - "npm-package-arg": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", - "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", - "requires": { - "hosted-git-info": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^6.0.0" - } + "proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==" } } }, @@ -28109,14 +28019,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==" }, - "cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "requires": { - "restore-cursor": "^5.0.0" - } - }, "emoji-regex": { "version": "10.6.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", @@ -28143,28 +28045,6 @@ } } }, - "onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "requires": { - "mimic-function": "^5.0.0" - } - }, - "restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "requires": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - } - }, - "signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" - }, "string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", @@ -28186,9 +28066,9 @@ } }, "ordered-binary": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.0.tgz", - "integrity": "sha512-IQh2aMfMIDbPjI/8a3Edr+PiOpcsB7yo8NdW7aHWVaoR/pcDldunMvnnwbk/auPGqmKeAdxtZl7MHX/QmPwhvQ==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.1.tgz", + "integrity": "sha512-QkCdPooczexPLiXIrbVOPYkR3VO3T6v2OyKRkR1Xbhpy7/LAVXwahnRCgRp78Oe/Ehf0C/HATAxfSr6eA1oX+w==", "optional": true }, "ospath": { @@ -28223,15 +28103,6 @@ } } }, - "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "optional": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, "p-retry": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", @@ -28254,58 +28125,34 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, - "package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" - }, "pacote": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.0.0.tgz", - "integrity": "sha512-lcqexq73AMv6QNLo7SOpz0JJoaGdS3rBFgF122NZVl1bApo2mfu+XzUBU/X/XsiJu+iUmKpekRayqQYAs+PhkA==", + "version": "21.0.4", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.0.4.tgz", + "integrity": "sha512-RplP/pDW0NNNDh3pnaoIWYPvNenS7UqMbXyvMqJczosiFWTeGGwJC2NQBLqKf4rGLFfwCOnntw1aEp9Jiqm1MA==", "requires": { - "@npmcli/git": "^6.0.0", - "@npmcli/installed-package-contents": "^3.0.0", - "@npmcli/package-json": "^6.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "@npmcli/run-script": "^9.0.0", - "cacache": "^19.0.0", + "@npmcli/git": "^7.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "@npmcli/run-script": "^10.0.0", + "cacache": "^20.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", - "npm-package-arg": "^12.0.0", - "npm-packlist": "^10.0.0", - "npm-pick-manifest": "^10.0.0", - "npm-registry-fetch": "^18.0.0", - "proc-log": "^5.0.0", + "npm-package-arg": "^13.0.0", + "npm-packlist": "^10.0.1", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", "promise-retry": "^2.0.1", - "sigstore": "^3.0.0", - "ssri": "^12.0.0", - "tar": "^6.1.11" + "sigstore": "^4.0.0", + "ssri": "^13.0.0", + "tar": "^7.5.5" }, "dependencies": { - "hosted-git-info": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", - "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", - "requires": { - "lru-cache": "^10.0.1" - } - }, - "lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" - }, - "npm-package-arg": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", - "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", - "requires": { - "hosted-git-info": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^6.0.0" - } + "proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==" } } }, @@ -28383,13 +28230,6 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "optional": true, - "peer": true - }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -28401,25 +28241,18 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", + "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", "requires": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "dependencies": { - "lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" - } + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" } }, "path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==" + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==" }, "pathval": { "version": "1.1.1", @@ -28454,9 +28287,9 @@ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==" }, "pify": { "version": "2.3.0", @@ -28473,21 +28306,15 @@ } }, "pkce-challenge": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", - "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==" }, "pngjs": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==" }, - "popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", - "peer": true - }, "portscanner": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.2.0.tgz", @@ -28510,9 +28337,9 @@ } }, "postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", + "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", "requires": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -28666,14 +28493,8 @@ "punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==" - }, - "qjobs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", - "optional": true, - "peer": true + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true }, "qrcode": { "version": "1.5.1", @@ -28707,11 +28528,11 @@ } }, "qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "requires": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" } }, "queue-microtask": { @@ -28719,49 +28540,49 @@ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "requires": { - "safe-buffer": "^5.1.0" - } - }, "range-parser": { "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": { - "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==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "dependencies": { + "http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "requires": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + } + }, + "statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==" + } } }, "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - } + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } }, "readdirp": { @@ -28900,13 +28721,27 @@ } }, "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "optional": true, + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "dependencies": { + "onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "requires": { + "mimic-function": "^5.0.0" + } + }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" + } } }, "retry": { @@ -28924,60 +28759,36 @@ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==" }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "optional": true, - "peer": true, - "requires": { - "glob": "^7.1.3" - }, - "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "optional": true, - "peer": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, "rollup": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.3.tgz", - "integrity": "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", "requires": { - "@rollup/rollup-android-arm-eabi": "4.52.3", - "@rollup/rollup-android-arm64": "4.52.3", - "@rollup/rollup-darwin-arm64": "4.52.3", - "@rollup/rollup-darwin-x64": "4.52.3", - "@rollup/rollup-freebsd-arm64": "4.52.3", - "@rollup/rollup-freebsd-x64": "4.52.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.52.3", - "@rollup/rollup-linux-arm-musleabihf": "4.52.3", - "@rollup/rollup-linux-arm64-gnu": "4.52.3", - "@rollup/rollup-linux-arm64-musl": "4.52.3", - "@rollup/rollup-linux-loong64-gnu": "4.52.3", - "@rollup/rollup-linux-ppc64-gnu": "4.52.3", - "@rollup/rollup-linux-riscv64-gnu": "4.52.3", - "@rollup/rollup-linux-riscv64-musl": "4.52.3", - "@rollup/rollup-linux-s390x-gnu": "4.52.3", - "@rollup/rollup-linux-x64-gnu": "4.52.3", - "@rollup/rollup-linux-x64-musl": "4.52.3", - "@rollup/rollup-openharmony-arm64": "4.52.3", - "@rollup/rollup-win32-arm64-msvc": "4.52.3", - "@rollup/rollup-win32-ia32-msvc": "4.52.3", - "@rollup/rollup-win32-x64-gnu": "4.52.3", - "@rollup/rollup-win32-x64-msvc": "4.52.3", + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", "@types/estree": "1.0.8", "fsevents": "~2.3.2" } @@ -29037,9 +28848,9 @@ } }, "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "safer-buffer": { "version": "2.1.2", @@ -29066,9 +28877,9 @@ } }, "immutable": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", - "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==" + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", + "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==" }, "readdirp": { "version": "4.1.2", @@ -29103,9 +28914,9 @@ }, "dependencies": { "ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -29213,12 +29024,9 @@ } }, "serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "requires": { - "randombytes": "^2.1.0" - } + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", + "integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==" }, "serve-index": { "version": "1.9.1", @@ -29331,9 +29139,9 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, "shell-quote": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", - "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==" + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz", + "integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==" }, "side-channel": { "version": "1.1.0", @@ -29386,16 +29194,16 @@ "optional": true }, "sigstore": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-3.1.0.tgz", - "integrity": "sha512-ZpzWAFHIFqyFE56dXqgX/DkDRZdz+rRcjoIk/RQU4IX0wiCv1l8S7ZrXDHcCc+uaf+6o7w3h2l3g6GYG5TKN9Q==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-4.1.0.tgz", + "integrity": "sha512-/fUgUhYghuLzVT/gaJoeVehLCgZiUxPCPMcyVNY0lIf/cTCz58K/WTI7PefDarXxp9nUKpEwg1yyz3eSBMTtgA==", "requires": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.4.0", - "@sigstore/sign": "^3.1.0", - "@sigstore/tuf": "^3.1.0", - "@sigstore/verify": "^2.1.0" + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0", + "@sigstore/sign": "^4.1.0", + "@sigstore/tuf": "^4.0.1", + "@sigstore/verify": "^3.1.0" } }, "sinon": { @@ -29413,9 +29221,9 @@ }, "dependencies": { "diff": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", - "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", + "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", "optional": true } } @@ -29428,14 +29236,30 @@ "requires": {} }, "slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", "optional": true, "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "optional": true + }, + "is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "optional": true, + "requires": { + "get-east-asian-width": "^1.3.1" + } + } } }, "smart-buffer": { @@ -29481,13 +29305,30 @@ } }, "socket.io-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", - "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", + "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", "devOptional": true, "requires": { "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" + "debug": "~4.4.1" + }, + "dependencies": { + "debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "devOptional": true, + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "devOptional": true + } } }, "sockjs": { @@ -29608,18 +29449,6 @@ "obuf": "^1.1.2", "readable-stream": "^3.0.6", "wbuf": "^1.7.3" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } } }, "split": { @@ -29649,9 +29478,9 @@ } }, "ssri": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", - "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.0.tgz", + "integrity": "sha512-yizwGBpbCn4YomB2lzhZqrHLJoqFGXihNbib3ozhqF/cIp5ue+xSmOQrjNasEE62hFxsCcg/V/z23t4n8jMEng==", "requires": { "minipass": "^7.0.3" } @@ -29715,24 +29544,12 @@ "limiter": "^1.0.5" } }, - "streamroller": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.0.2.tgz", - "integrity": "sha512-ur6y5S5dopOaRXBuRIZ1u6GC5bcEXHRZKgfBjfCglMhmIf+roVCECjvkEYzNQOXIN2/JPnkMPW/8B3CZoKaEPA==", - "optional": true, - "peer": true, - "requires": { - "date-format": "^4.0.3", - "debug": "^4.1.1", - "fs-extra": "^10.0.0" - } - }, "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "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==", "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "~5.2.0" } }, "string-width": { @@ -29745,16 +29562,6 @@ "strip-ansi": "^6.0.1" } }, - "string-width-cjs": { - "version": "npm:string-width@4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -29763,14 +29570,6 @@ "ansi-regex": "^5.0.1" } }, - "strip-ansi-cjs": { - "version": "npm:strip-ansi@6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - }, "strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -29798,82 +29597,32 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, "systeminformation": { - "version": "5.27.7", - "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.27.7.tgz", - "integrity": "sha512-saaqOoVEEFaux4v0K8Q7caiauRwjXC4XbD2eH60dxHXbpKxQ8kH9Rf7Jh+nryKpOUSEFxtCdBlSUx0/lO6rwRg==", + "version": "5.31.1", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.31.1.tgz", + "integrity": "sha512-6pRwxoGeV/roJYpsfcP6tN9mep6pPeCtXbUOCdVa0nme05Brwcwdge/fVNhIZn2wuUitAKZm4IYa7QjnRIa9zA==", "optional": true }, "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==" }, "tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "version": "7.5.16", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz", + "integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==", "requires": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "dependencies": { - "fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "requires": { - "minipass": "^3.0.0" - }, - "dependencies": { - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "requires": { - "yallist": "^4.0.0" - } - } - } - }, - "minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==" - }, - "minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "requires": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "dependencies": { - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "requires": { - "yallist": "^4.0.0" - } - } - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" - } + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" } }, "terser": { - "version": "5.44.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", - "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", "requires": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -29882,14 +29631,13 @@ } }, "terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", + "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", "requires": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", "terser": "^5.31.1" }, "dependencies": { @@ -29948,9 +29696,9 @@ "requires": {} }, "picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==" + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" } } }, @@ -29969,11 +29717,6 @@ "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", "optional": true }, - "tlite": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/tlite/-/tlite-0.1.9.tgz", - "integrity": "sha512-5QOBAvDxZZwW1i+2YXMgF6/PuV/KhA0LyE9PyVi8Ywr3bfIPziZcQD+RpdJaQurCU8zIGtBo/XuPCEHdvyeFuQ==" - }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -30049,13 +29792,13 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, "tuf-js": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-3.1.0.tgz", - "integrity": "sha512-3T3T04WzowbwV2FDiGXBbr81t64g1MUGGJRgT4x5o97N+8ArdhVCAF9IxFrxuSJmM3E5Asn7nKHkao0ibcZXAg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-4.1.0.tgz", + "integrity": "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ==", "requires": { - "@tufjs/models": "3.0.1", - "debug": "^4.4.1", - "make-fetch-happen": "^14.0.3" + "@tufjs/models": "4.1.0", + "debug": "^4.4.3", + "make-fetch-happen": "^15.0.1" }, "dependencies": { "debug": { @@ -30103,12 +29846,6 @@ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "optional": true }, - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "optional": true - }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -30128,13 +29865,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==" }, - "ua-parser-js": { - "version": "0.7.35", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.35.tgz", - "integrity": "sha512-veRf7dawaj9xaWEu9HoTVn5Pggtc/qj+kqTOFvNiN1l0YdxwC1kvel57UCjThjGa3BHBihE8/UJAHI+uQHmd/g==", - "optional": true, - "peer": true - }, "undici-types": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", @@ -30165,17 +29895,17 @@ "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==" }, "unique-filename": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", - "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-5.0.0.tgz", + "integrity": "sha512-2RaJTAvAb4owyjllTfXzFClJ7WsGxlykkPvCr9pA//LD9goVq+m4PPAeBgNodGZ7nSrntT/auWpJ6Y5IFXcfjg==", "requires": { - "unique-slug": "^5.0.0" + "unique-slug": "^6.0.0" } }, "unique-slug": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", - "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-6.0.0.tgz", + "integrity": "sha512-4Lup7Ezn8W3d52/xBhZBVdx323ckxa7DEvd9kPQHppTkLoJXw6ltrBCyj5pnrxj0qKDxYMJ56CoxNuFCscdTiw==", "requires": { "imurmurhash": "^0.1.4" } @@ -30198,9 +29928,9 @@ "optional": true }, "update-browserslist-db": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", - "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "requires": { "escalade": "^3.2.0", "picocolors": "^1.1.1" @@ -30210,6 +29940,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", + "dev": true, "requires": { "punycode": "^2.1.0" } @@ -30266,11 +29997,11 @@ } }, "vite": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.11.tgz", - "integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", + "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "requires": { - "esbuild": "^0.25.0", + "esbuild": "^0.27.0", "fdir": "^6.5.0", "fsevents": "~2.3.3", "picomatch": "^4.0.3", @@ -30286,9 +30017,9 @@ "requires": {} }, "picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==" + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, "tinyglobby": { "version": "0.2.15", @@ -30301,13 +30032,6 @@ } } }, - "void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", - "optional": true, - "peer": true - }, "wait-on": { "version": "8.0.5", "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-8.0.5.tgz", @@ -30345,9 +30069,9 @@ "optional": true }, "webpack": { - "version": "5.101.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.2.tgz", - "integrity": "sha512-4JLXU0tD6OZNVqlwzm3HGEhAHufSiyv+skb7q0d2367VDMzrU1Q/ZeepvkcHH0rZie6uqEtTQQe0OEOOluH3Mg==", + "version": "5.105.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", + "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", "requires": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -30357,22 +30081,22 @@ "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.15.0", "acorn-import-phases": "^1.0.3", - "browserslist": "^4.24.0", + "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.3", - "es-module-lexer": "^1.2.1", + "enhanced-resolve": "^5.19.0", + "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", + "loader-runner": "^4.3.1", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^4.3.2", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.11", - "watchpack": "^2.4.1", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.16", + "watchpack": "^2.5.1", "webpack-sources": "^3.3.3" }, "dependencies": { @@ -30381,6 +30105,15 @@ "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "requires": {} + }, + "watchpack": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "requires": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + } } } }, @@ -30455,15 +30188,10 @@ "safe-buffer": "5.2.1" } }, - "cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==" - }, "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" }, "debug": { "version": "2.6.9", @@ -30479,61 +30207,61 @@ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" }, "express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "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", + "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", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", "requires": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "statuses": "2.0.1", + "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "ipaddr.js": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", - "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", + "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==" }, "merge-descriptors": { "version": "1.0.3", @@ -30554,24 +30282,19 @@ } }, "path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==" }, "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==" }, "ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "requires": {} } } @@ -30648,16 +30371,6 @@ "strip-ansi": "^6.0.0" } }, - "wrap-ansi-cjs": { - "version": "npm:wrap-ansi@7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -30695,63 +30408,85 @@ "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==" }, "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==" }, "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "optional": true, - "peer": true, + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", "requires": { - "cliui": "^7.0.2", + "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^7.2.0", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^22.0.0" }, "dependencies": { + "ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==" + }, + "ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==" + }, "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "optional": true, - "peer": true, + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + } + }, + "emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==" + }, + "string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "requires": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + } + }, + "strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "requires": { + "ansi-regex": "^6.2.2" } }, "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "optional": true, - "peer": true, + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" } }, "y18n": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.6.tgz", - "integrity": "sha512-PlVX4Y0lDTN6E2V4ES2tEdyvXkeKzxa8c/vo0pxPr/TqbztddTP0yn7zZylIyiAuxerqj0Q5GhpJ1YJCP8LaZQ==", - "optional": true, - "peer": true + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" }, "yargs-parser": { - "version": "20.2.7", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz", - "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==", - "optional": true, - "peer": true + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==" } } }, @@ -30765,13 +30500,12 @@ } }, "yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.4.0.tgz", + "integrity": "sha512-jIH9yLR9wqr0wOS0TpBvo/g/2UgZH5qePVbjgRliiF0BYvOZyaBknKsF+x9Iht0O6sqgnB93rCICdOZFecJuDw==", "optional": true, "requires": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" + "pend": "~1.2.0" } }, "yn": { @@ -30791,14 +30525,14 @@ "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==" }, "zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==" + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==" }, "zod-to-json-schema": { - "version": "3.24.6", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", - "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", "requires": {} }, "zone.js": { @@ -30807,9 +30541,9 @@ "integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==" }, "zrender": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.4.4.tgz", - "integrity": "sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-6.1.0.tgz", + "integrity": "sha512-oEGMDB6pOP2S6OwRR4PdVv610zrjnA3Bh+JnSG12fYJlBKjtNAoEb5fSUoCOOINlH96I2fU38/A2UpRKs67xYQ==", "requires": { "tslib": "2.3.0" }, diff --git a/frontend/package.json b/frontend/package.json index 37d479b6a..9fe8f89ef 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "mempool-frontend", - "version": "3.3-dev", + "version": "3.4-dev", "description": "Bitcoin mempool visualizer and blockchain explorer backend", "license": "GNU Affero General Public License v3.0", "homepage": "https://mempool.space", @@ -24,16 +24,18 @@ "tsc": "./node_modules/typescript/bin/tsc", "i18n-extract-from-source": "npm run ng -- extract-i18n --out-file ./src/locale/messages.xlf", "i18n-pull-from-transifex": "tx pull -a --minimum-perc 1 --force", - "serve": "npm run generate-config && npm run ng -- serve -c local", - "serve:local-prod": "npm run generate-config && npm run ng -- serve -c local-prod", - "serve:parameterized": "npm run generate-config && npm run ng -- serve -c parameterized", - "start": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local", - "start:parameterized": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c parameterized", - "start:local-esplora": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-esplora", - "start:local-prod": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-prod", - "start:mixed": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c mixed", - "build": "npm run generate-config && npm run ng -- build --configuration production --localize && npm run sync-assets-dev && npm run sync-assets", - "sync-assets": "rsync -av ./src/resources ./dist/mempool/browser && node sync-assets.js 'dist/mempool/browser/resources/'", + "serve": "npm run generate-themes && npm run generate-config && npm run ng -- serve -c local", + "serve:local-prod": "npm run generate-themes && npm run generate-config && npm run ng -- serve -c local-prod", + "serve:parameterized": "npm run generate-themes && npm run generate-config && npm run ng -- serve -c parameterized", + "start": "npm run generate-themes && npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local", + "start:parameterized": "npm run generate-themes && npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c parameterized", + "start:local-esplora": "npm run generate-themes && npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-esplora", + "start:local-prod": "npm run generate-themes && npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-prod", + "start:mixed": "npm run generate-themes && npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c mixed", + "build": "npm run generate-themes && npm run generate-config && npm run ng -- build --configuration production --localize && npm run sync-assets-dev && npm run sync-assets", + "generate-themes": "node generate-themes.js", + "copy-themes": "node generate-themes.js copy", + "sync-assets": "npm run copy-themes && rsync -av ./src/resources ./dist/mempool/browser && node sync-assets.js 'dist/mempool/browser/resources/'", "sync-assets-dev": "node sync-assets.js 'src/resources/'", "generate-config": "node generate-config.js", "test": "npm run ng -- test", @@ -57,41 +59,40 @@ "cypress:run:ci:parameterized": "node update-config.js TESTNET_ENABLED=true TESTNET4_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:parameterized 4200 cypress:run:record" }, "dependencies": { - "@angular-devkit/build-angular": "^20.3.12", - "@angular/animations": "^20.3.14", - "@angular/cli": "^20.3.12", - "@angular/common": "^20.3.14", - "@angular/compiler": "^20.3.14", - "@angular/core": "^20.3.14", - "@angular/forms": "^20.3.14", - "@angular/localize": "^20.3.14", - "@angular/platform-browser": "^20.3.14", - "@angular/platform-browser-dynamic": "^20.3.14", - "@angular/platform-server": "^20.3.14", - "@angular/router": "^20.3.14", - "@angular/ssr": "^20.3.12", + "@angular-devkit/build-angular": "^20.3.25", + "@angular/animations": "^20.3.25", + "@angular/cli": "^20.3.25", + "@angular/common": "^20.3.25", + "@angular/compiler": "^20.3.25", + "@angular/core": "^20.3.25", + "@angular/forms": "^20.3.25", + "@angular/localize": "^20.3.25", + "@angular/platform-browser": "^20.3.25", + "@angular/platform-browser-dynamic": "^20.3.25", + "@angular/platform-server": "^20.3.25", + "@angular/router": "^20.3.25", + "@angular/ssr": "^20.3.25", "@fortawesome/angular-fontawesome": "^3.0.0", "@fortawesome/fontawesome-common-types": "~6.7.2", "@fortawesome/fontawesome-svg-core": "~6.7.2", "@fortawesome/free-solid-svg-icons": "~6.7.2", "@ng-bootstrap/ng-bootstrap": "^19.0.0", "@types/qrcode": "~1.5.0", - "bootstrap": "~4.6.2", + "@noble/secp256k1": "^3.1.0", + "bootstrap": "~5.3.8", "clipboard": "^2.0.11", "domino": "^2.1.6", - "echarts": "~5.4.0", + "echarts": "~6.1.0", "ngx-echarts": "~20.0.2", "ngx-infinite-scroll": "^20.0.0", "qrcode": "1.5.1", "rxjs": "~7.8.1", - "esbuild": "^0.25.8", - "tlite": "^0.1.9", "tslib": "~2.8.0", "zone.js": "~0.15.1" }, "devDependencies": { - "@angular/compiler-cli": "^20.3.14", - "@angular/language-service": "^20.3.14", + "@angular/compiler-cli": "^20.3.25", + "@angular/language-service": "^20.3.25", "@types/node": "^24.9.2", "@typescript-eslint/eslint-plugin": "^8.46.2", "@typescript-eslint/parser": "^8.46.2", @@ -105,12 +106,15 @@ }, "optionalDependencies": { "@cypress/schematic": "^2.5.0", - "cypress": "^15.7.0", + "cypress": "^15.16.0", "cypress-fail-on-console-error": "~5.1.1", "cypress-wait-until": "^3.0.1", "mock-socket": "~9.3.1", "start-server-and-test": "~2.1.2" }, + "overrides": { + "tar": "^7.5.5" + }, "scarfSettings": { "enabled": false } diff --git a/frontend/proxy.conf.js b/frontend/proxy.conf.js index 05f7550e0..1ef2f6934 100644 --- a/frontend/proxy.conf.js +++ b/frontend/proxy.conf.js @@ -24,7 +24,7 @@ PROXY_CONFIG = [ '/api/**', '!/api/v1/ws', '!/liquid', '!/liquid/**', '!/liquid/', '!/liquidtestnet', '!/liquidtestnet/**', '!/liquidtestnet/', - '/testnet/api/**', '/signet/api/**', '/testnet4/api/**' + '/testnet/api/**', '/signet/api/**', '/testnet4/api/**', '/regtest/api/**' ], target: "https://mempool.space", ws: true, diff --git a/frontend/proxy.conf.local-esplora.js b/frontend/proxy.conf.local-esplora.js index 905910294..34acd1cb0 100644 --- a/frontend/proxy.conf.local-esplora.js +++ b/frontend/proxy.conf.local-esplora.js @@ -78,6 +78,27 @@ PROXY_CONFIG.push(...[ "^/testnet": "" }, }, + { + context: ['/regtest/api/v1/**'], + target: `http://127.0.0.1:8999`, + secure: false, + ws: true, + changeOrigin: true, + proxyTimeout: 30000, + pathRewrite: { + "^/regtest": "" + }, + }, + { + context: ['/regtest/api/**'], + target: `http://127.0.0.1:3000`, + secure: false, + changeOrigin: true, + proxyTimeout: 30000, + pathRewrite: { + "^/regtest/api": "" + }, + }, /* Optional proxy to route dev to official acceleration services { context: ['/api/v1/services/accelerator/**'], diff --git a/frontend/proxy.conf.local.js b/frontend/proxy.conf.local.js index e7bfeee70..900629c0c 100644 --- a/frontend/proxy.conf.local.js +++ b/frontend/proxy.conf.local.js @@ -78,6 +78,27 @@ PROXY_CONFIG.push(...[ "^/testnet": "" }, }, + { + context: ['/regtest/api/v1/**'], + target: `http://localhost:8999`, + secure: false, + ws: true, + changeOrigin: true, + proxyTimeout: 30000, + pathRewrite: { + "^/regtest": "" + }, + }, + { + context: ['/regtest/api/**'], + target: `http://localhost:8999`, + secure: false, + changeOrigin: true, + proxyTimeout: 30000, + pathRewrite: { + "^/regtest/api/": "/api/v1/" + }, + }, { context: ['/api/v1/services/**'], target: `http://localhost:9000`, diff --git a/frontend/proxy.conf.mixed.js b/frontend/proxy.conf.mixed.js index db8af5d95..799ce337e 100644 --- a/frontend/proxy.conf.mixed.js +++ b/frontend/proxy.conf.mixed.js @@ -62,6 +62,27 @@ if (configContent && configContent.BASE_MODULE === 'liquid') { } PROXY_CONFIG.push(...[ + { + context: ['/regtest/api/v1/**'], + target: `http://localhost:8999`, + secure: false, + ws: true, + changeOrigin: true, + proxyTimeout: 30000, + pathRewrite: { + "^/regtest": "" + }, + }, + { + context: ['/regtest/api/**'], + target: `http://localhost:8999`, + secure: false, + changeOrigin: true, + proxyTimeout: 30000, + pathRewrite: { + "^/regtest/api/": "/api/v1/" + }, + }, { context: ['/api/v1/services/**'], target: `http://localhost:9000`, diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 5f69b55cf..80efc5d6f 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -1,6 +1,6 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; -import { AppPreloadingStrategy } from '@app/app.preloading-strategy' +import { AppPreloadingStrategy } from '@app/app.preloading-strategy'; import { BlockViewComponent } from '@components/block-view/block-view.component'; import { EightBlocksComponent } from '@components/eight-blocks/eight-blocks.component'; import { MempoolBlockViewComponent } from '@components/mempool-block-view/mempool-block-view.component'; @@ -9,11 +9,10 @@ import { StatusViewComponent } from '@components/status-view/status-view.compone import { AddressGroupComponent } from '@components/address-group/address-group.component'; import { TrackerGuard } from '@app/route-guards'; -const browserWindow = window || {}; -// @ts-ignore +const browserWindow = window as typeof window & { __env?: any }; const browserWindowEnv = browserWindow.__env || {}; -let routes: Routes = [ +const testnetRoutes: Routes = browserWindowEnv.TESTNET_ENABLED ? [ { path: 'testnet', children: [ @@ -52,6 +51,9 @@ let routes: Routes = [ }, ] }, +] : []; + +const testnet4Routes: Routes = browserWindowEnv.TESTNET4_ENABLED ? [ { path: 'testnet4', children: [ @@ -90,6 +92,9 @@ let routes: Routes = [ }, ] }, +] : []; + +const signetRoutes: Routes = browserWindowEnv.SIGNET_ENABLED ? [ { path: 'signet', children: [ @@ -133,6 +138,59 @@ let routes: Routes = [ }, ] }, +] : []; + +const regtestRoutes: Routes = browserWindowEnv.REGTEST_ENABLED ? [ + { + path: 'regtest', + children: [ + { + path: 'mining/blocks', + redirectTo: 'blocks', + pathMatch: 'full' + }, + { + path: '', + pathMatch: 'full', + loadChildren: () => import('@app/bitcoin-graphs.module').then(m => m.BitcoinGraphsModule), + data: { preload: true }, + }, + { + path: '', + loadChildren: () => import('@app/master-page.module').then(m => m.MasterPageModule), + data: { preload: true }, + }, + { + path: 'widget/wallet', + children: [], + component: AddressGroupComponent, + data: { + networkSpecific: true, + } + }, + { + path: 'status', + data: { networks: ['bitcoin', 'liquid'] }, + component: StatusViewComponent + }, + { + path: '', + loadChildren: () => import('@app/bitcoin-graphs.module').then(m => m.BitcoinGraphsModule), + data: { preload: true }, + }, + { + path: '**', + redirectTo: '/regtest' + }, + ] + }, +] : []; + +let routes: Routes = [ + ...testnetRoutes, + ...testnet4Routes, + ...signetRoutes, + ...regtestRoutes, { path: '', pathMatch: 'full', @@ -177,6 +235,10 @@ let routes: Routes = [ path: 'signet', loadChildren: () => import('@app/previews.module').then(m => m.PreviewsModule) }, + { + path: 'regtest', + loadChildren: () => import('@app/previews.module').then(m => m.PreviewsModule) + }, ], }, { @@ -215,46 +277,50 @@ let routes: Routes = [ }, ]; +const liquidTestnetRoutes: Routes = browserWindowEnv.LIQUID_TESTNET_ENABLED ? [ + { + path: 'testnet', + children: [ + { + path: '', + pathMatch: 'full', + loadChildren: () => import('@app/liquid/liquid-graphs.module').then(m => m.LiquidGraphsModule), + data: { preload: true }, + }, + { + path: '', + loadChildren: () => import ('@app/liquid/liquid-master-page.module').then(m => m.LiquidMasterPageModule), + data: { preload: true }, + }, + { + path: 'widget/wallet', + children: [], + component: AddressGroupComponent, + data: { + networkSpecific: true, + } + }, + { + path: 'status', + data: { networks: ['bitcoin', 'liquid'] }, + component: StatusViewComponent + }, + { + path: '', + loadChildren: () => import('@app/liquid/liquid-graphs.module').then(m => m.LiquidGraphsModule), + data: { preload: true }, + }, + { + path: '**', + redirectTo: '/testnet' + }, + ] + }, +] : []; + if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') { routes = [ - { - path: 'testnet', - children: [ - { - path: '', - pathMatch: 'full', - loadChildren: () => import('@app/liquid/liquid-graphs.module').then(m => m.LiquidGraphsModule), - data: { preload: true }, - }, - { - path: '', - loadChildren: () => import ('@app/liquid/liquid-master-page.module').then(m => m.LiquidMasterPageModule), - data: { preload: true }, - }, - { - path: 'widget/wallet', - children: [], - component: AddressGroupComponent, - data: { - networkSpecific: true, - } - }, - { - path: 'status', - data: { networks: ['bitcoin', 'liquid'] }, - component: StatusViewComponent - }, - { - path: '', - loadChildren: () => import('@app/liquid/liquid-graphs.module').then(m => m.LiquidGraphsModule), - data: { preload: true }, - }, - { - path: '**', - redirectTo: '/signet' - }, - ] - }, + ...liquidTestnetRoutes, { path: '', pathMatch: 'full', @@ -310,7 +376,7 @@ if (!window['isMempoolSpaceBuild']) { @NgModule({ imports: [RouterModule.forRoot(routes, { initialNavigation: 'enabledBlocking', - scrollPositionRestoration: 'enabled', + scrollPositionRestoration: 'disabled', anchorScrolling: 'disabled', preloadingStrategy: AppPreloadingStrategy })], diff --git a/frontend/src/app/app.constants.ts b/frontend/src/app/app.constants.ts index 2b6661fcc..8bf6b73a2 100644 --- a/frontend/src/app/app.constants.ts +++ b/frontend/src/app/app.constants.ts @@ -82,46 +82,88 @@ export const contrastMempoolFeeColors = [ 'ffb700', ]; + export const lightMempoolFeeColors = [ + '49c284', + '99c246', + 'a0c343', + 'a7c240', + 'b0c13c', + 'b8c03b', + 'bebe3a', + 'beb238', + 'bcac37', + 'bba336', + 'b99e35', + 'b79334', + 'b68f33', + 'b68a32', + 'b48430', + 'b17f30', + 'b07b2f', + 'ae722e', + 'ad6b2d', + 'ab652b', + 'aa5b2a', + 'a95629', + 'a74c28', + 'a34126', + 'a13a25', + 'a02d24', + '9e2423', + '9c222a', + '9c2132', + '9b203a', + '99203e', + '972043', + '951f45', + '931e48', + '921d49', + '921c4e', + '901b50', + '8e1a53', + '8b1954', +]; + export const chartColors = [ - "#A81524", - "#D81B60", - "#8E24AA", - "#5E35B1", - "#3949AB", - "#1E88E5", - "#039BE5", - "#00ACC1", - "#00897B", - "#43A047", - "#7CB342", - "#C0CA33", - "#FDD835", - "#FFB300", - "#FB8C00", - "#F4511E", - "#6D4C41", - "#757575", - "#546E7A", - "#b71c1c", - "#880E4F", - "#4A148C", - "#311B92", - "#1A237E", - "#0D47A1", - "#01579B", - "#006064", - "#004D40", - "#1B5E20", - "#33691E", - "#827717", - "#F57F17", - "#FF6F00", - "#E65100", - "#BF360C", - "#3E2723", - "#212121", - "#263238", - "#801313", + '#A81524', + '#D81B60', + '#8E24AA', + '#5E35B1', + '#3949AB', + '#1E88E5', + '#039BE5', + '#00ACC1', + '#00897B', + '#43A047', + '#7CB342', + '#C0CA33', + '#FDD835', + '#FFB300', + '#FB8C00', + '#F4511E', + '#6D4C41', + '#757575', + '#546E7A', + '#b71c1c', + '#880E4F', + '#4A148C', + '#311B92', + '#1A237E', + '#0D47A1', + '#01579B', + '#006064', + '#004D40', + '#1B5E20', + '#33691E', + '#827717', + '#F57F17', + '#FF6F00', + '#E65100', + '#BF360C', + '#3E2723', + '#212121', + '#263238', + '#801313', ]; export const originalChartColors = chartColors.slice(1); diff --git a/frontend/src/app/app.preloading-strategy.ts b/frontend/src/app/app.preloading-strategy.ts index f62d072da..dc8c56232 100644 --- a/frontend/src/app/app.preloading-strategy.ts +++ b/frontend/src/app/app.preloading-strategy.ts @@ -3,7 +3,7 @@ import { Observable, timer, mergeMap, of } from 'rxjs'; export class AppPreloadingStrategy implements PreloadingStrategy { preload(route: Route, load: Function): Observable { - return route.data && route.data.preload + return route.data && route.data.preload ? timer(1500).pipe(mergeMap(() => load())) : of(null); } diff --git a/frontend/src/app/components/about/about-sponsors.component.html b/frontend/src/app/components/about/about-sponsors.component.html index 9471fc78f..02189acb6 100644 --- a/frontend/src/app/components/about/about-sponsors.component.html +++ b/frontend/src/app/components/about/about-sponsors.component.html @@ -1,14 +1,14 @@

If you're an individual...

- Become a Community Sponsor + Become a Community Sponsor

If you're a business...

- Become an Enterprise Sponsor + Become an Enterprise Sponsor diff --git a/frontend/src/app/components/about/about-sponsors.component.ts b/frontend/src/app/components/about/about-sponsors.component.ts index be86a9d93..673bb207c 100644 --- a/frontend/src/app/components/about/about-sponsors.component.ts +++ b/frontend/src/app/components/about/about-sponsors.component.ts @@ -1,5 +1,4 @@ import { Component, Input } from '@angular/core'; -import { EnterpriseService } from '@app/services/enterprise.service'; @Component({ selector: 'app-about-sponsors', @@ -10,17 +9,4 @@ import { EnterpriseService } from '@app/services/enterprise.service'; export class AboutSponsorsComponent { @Input() host = 'https://mempool.space'; @Input() context = 'about'; - - constructor(private enterpriseService: EnterpriseService) { - } - - onSponsorClick(e): boolean { - this.enterpriseService.goal(5); - return true; - } - - onEnterpriseClick(e): boolean { - this.enterpriseService.goal(6); - return true; - } } diff --git a/frontend/src/app/components/about/about.component.html b/frontend/src/app/components/about/about.component.html index 7c4e11218..1012d6823 100644 --- a/frontend/src/app/components/about/about.component.html +++ b/frontend/src/app/components/about/about.component.html @@ -2,7 +2,11 @@
® - + @if (isLightMode) { + + } @else { + + }
v{{ packetJsonVersion }} [{{ frontendGitCommitHash }}] [{{ stateService.env.GIT_COMMIT_HASH_MEMPOOL_SPACE }}] @@ -41,15 +45,289 @@

Enterprise Sponsors 🚀

- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - Spiral @@ -126,13 +404,23 @@ Blockstream - + @if (isLightMode) { + + + + } @else { + + } Unchained - + @if (isLightMode) { + + } @else { + + } Bitkey @@ -207,8 +495,24 @@ Gemini + + + Onramp Bitcoin + + + @if (isLightMode) { + + } @else { + + } + Cake Wallet + - + @if (isLightMode) { + + } @else { + + } Leather @@ -286,7 +590,11 @@ NixOS - + @if (isLightMode) { + + } @else { + + } StartOS @@ -374,17 +682,42 @@

- The Mempool Open Source Project®, Mempool Accelerator®, Mempool Enterprise®, Mempool Wallet™, mempool.space®, Be your own explorer™, Explore the full Bitcoin ecosystem®, Mempool Goggles™, the mempool Logo, the mempool Square Logo, the mempool block visualization Logo, the mempool Blocks Logo, the mempool transaction Logo, the mempool Blocks 3 | 2 Logo, the mempool research Logo, the mempool.space Vertical Logo, and the mempool.space Horizontal Logo are either registered trademarks or trademarks of Mempool Holdings S.A. de C.V. in Japan, the United States, and/or other countries. + The Mempool Open Source Project®, Mempool Accelerator®, Mempool Enterprise®, Mempool Wallet™, mempool.space®, Be your own explorer™, Explore the full Bitcoin ecosystem®, Mempool Goggles®, the mempool Logo, the mempool Square Logo, the mempool block visualization Logo, the mempool Blocks Logo, the mempool transaction Logo, the mempool Blocks 3 | 2 Logo, the mempool research Logo, the mempool.space Vertical Logo, and the mempool.space Horizontal Logo are either registered trademarks or trademarks of Mempool Holdings S.A. de C.V. in Japan, the United States, and/or other countries.

While our software is available under an open source software license, the copyright license does not include an implied right or license to use our trademarks. See our Trademark Policy and Guidelines for more details, published on <https://mempool.space/trademark-policy>. diff --git a/frontend/src/app/components/about/about.component.scss b/frontend/src/app/components/about/about.component.scss index 099581a15..471cf828d 100644 --- a/frontend/src/app/components/about/about.component.scss +++ b/frontend/src/app/components/about/about.component.scss @@ -101,42 +101,30 @@ } } - .alliances { - margin-bottom: 100px; - a { - &:nth-child(3) { - position: relative; - top: 10px; - } + .alliances-logos { + align-items: center; + margin-bottom: 80px; + justify-content: center; + } + .alliance-logo { + height: 80px; + display: flex; + justify-content: center; + margin-top: 25px; + @media (min-width: 768px) { + margin-top: 45px; } - img { - display: inline-block; - margin: 15px auto; - height: 62px; - @media (min-width: 425px) { - margin: 15px 60px; - } - @media (min-width: 576px) { - margin: 15px 120px; - } - @media (min-width: 850px) { - margin: 50px 30px 0px; - } - } - .liquid { - top: 7px; - position: relative; - } - .copa { - height: auto; - top: 23px; - position: relative; + } + .stratum-v2-logo { + object-fit: contain; + @media (max-width: 767px) { width: 300px; } - .sv { - height: 85px; - width: auto; + } + .copa { + @media (min-width: 992px) { position: relative; + top: 15px; } } @@ -147,6 +135,7 @@ .community-integrations-sponsor, .maintainers { scroll-margin: 30px; + scroll-margin-top: 65px; .wrapper { display: inline-block; a { @@ -198,7 +187,7 @@ margin: auto; line-height: 1.8; font-size: 87.5%; - color: #e83e8c; + color: var(--pink); word-wrap: break-word; font-family: SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace; ul { @@ -272,6 +261,6 @@ display: flex; flex-wrap: wrap; justify-content: center; - max-width: 850px; + max-width: 1050px; } } diff --git a/frontend/src/app/components/about/about.component.ts b/frontend/src/app/components/about/about.component.ts index 01d496c04..211f21622 100644 --- a/frontend/src/app/components/about/about.component.ts +++ b/frontend/src/app/components/about/about.component.ts @@ -1,16 +1,16 @@ -import { ChangeDetectionStrategy, Component, ElementRef, Inject, LOCALE_ID, OnInit, ViewChild } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Inject, LOCALE_ID, OnInit, ViewChild } from '@angular/core'; import { WebsocketService } from '@app/services/websocket.service'; import { SeoService } from '@app/services/seo.service'; import { OpenGraphService } from '@app/services/opengraph.service'; import { StateService } from '@app/services/state.service'; -import { Observable } from 'rxjs'; +import { Observable, Subscription } from 'rxjs'; import { ApiService } from '@app/services/api.service'; import { IBackendInfo } from '@interfaces/websocket.interface'; import { Router, ActivatedRoute } from '@angular/router'; import { map, share, tap } from 'rxjs/operators'; import { ITranslators } from '@interfaces/node-api.interface'; import { DOCUMENT } from '@angular/common'; -import { EnterpriseService } from '@app/services/enterprise.service'; +import { ThemeService } from '../../services/theme.service'; @Component({ selector: 'app-about', @@ -26,6 +26,8 @@ export class AboutComponent implements OnInit { packetJsonVersion = this.stateService.env.PACKAGE_JSON_VERSION; officialMempoolSpace = this.stateService.env.OFFICIAL_MEMPOOL_SPACE; showNavigateToSponsor = false; + themeStateSubscription: Subscription; + loadedTheme = 'default'; profiles$: Observable; translators$: Observable; @@ -37,10 +39,11 @@ export class AboutComponent implements OnInit { private seoService: SeoService, private ogService: OpenGraphService, public stateService: StateService, - private enterpriseService: EnterpriseService, private apiService: ApiService, private router: Router, private route: ActivatedRoute, + private themeService: ThemeService, + private cd: ChangeDetectorRef, @Inject(LOCALE_ID) public locale: string, @Inject(DOCUMENT) private document: Document, ) { } @@ -58,11 +61,11 @@ export class AboutComponent implements OnInit { if (scrollToSponsors && !profiles?.whales?.length && !profiles?.chads?.length) { return; } else { - this.goToAnchor(scrollToSponsors) + this.goToAnchor(scrollToSponsors); } }), share(), - ) + ); this.translators$ = this.apiService.getTranslators$() .pipe( @@ -88,6 +91,14 @@ export class AboutComponent implements OnInit { }), tap(() => this.goToAnchor()) ); + + this.themeStateSubscription = this.themeService.themeState$.subscribe((state) => { + if (state.loading) { + return; + } + this.loadedTheme = state.theme; + this.cd.markForCheck(); + }); } ngAfterViewInit() { @@ -128,13 +139,11 @@ export class AboutComponent implements OnInit { this.promoVideo.nativeElement.muted = false; } - onSponsorClick(e): boolean { - this.enterpriseService.goal(5); - return true; + get isLightMode(): boolean { + return this.loadedTheme === 'nymkappa'; } - onEnterpriseClick(e): boolean { - this.enterpriseService.goal(6); - return true; + ngOnDestroy(): void { + this.themeStateSubscription?.unsubscribe(); } } diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html index 339a0a196..54fe158db 100644 --- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html +++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html @@ -32,7 +32,6 @@

-
+
@@ -112,7 +111,7 @@ - @if (hasAccessToBalanceMode) { + @if (estimate.isProUser) { Next block market rate @@ -129,7 +128,7 @@ sats - + } @@ -151,7 +150,7 @@ sats - + } @@ -169,7 +168,7 @@ sats - + @@ -181,25 +180,25 @@ sats - + - + - Estimated acceleration cost ~{{ estimate.targetFeeRate | number : '1.0-0' }} sat/vB + Estimated acceleration cost ~{{ estimate.targetFeeRate | number : '1.0-0' }} sat/vB - + {{ estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee | number }} sats - + @@ -208,28 +207,28 @@ - @if (hasAccessToBalanceMode) { - Maximum acceleration cost + @if (estimate.isProUser) { + Maximum acceleration cost } @else { - Acceleration cost + Acceleration cost } - + {{ cost | number }} sats - - + + - + Available balance @@ -237,15 +236,19 @@ sats - + - - + + + @if (partnerCode) { + + } +
@@ -287,10 +290,10 @@
-
+
-
+