diff --git a/.github/workflows/development-cleanup.yml b/.github/workflows/development-cleanup.yml new file mode 100644 index 00000000..b40f7431 --- /dev/null +++ b/.github/workflows/development-cleanup.yml @@ -0,0 +1,87 @@ +name: Cleanup Dev + +on: + pull_request: + types: [closed] + +jobs: + cleanup-ui-pr-preview: + permissions: + contents: write + id-token: 'write' + issues: write + runs-on: ubuntu-latest + + steps: + - name: Check out gh-pages branch + uses: actions/checkout@v3 + with: + ref: gh-pages + fetch-depth: 1 + + - name: Check if preview directory exists + id: check-preview + run: | + if [ -d "ui/pr/${{ github.event.pull_request.number }}" ]; then + echo "preview_exists=true" >> $GITHUB_OUTPUT + echo "Preview directory exists for PR #${{ github.event.pull_request.number }}" + else + echo "preview_exists=false" >> $GITHUB_OUTPUT + echo "No preview directory found for PR #${{ github.event.pull_request.number }}" + fi + + - name: Create an empty directory for cleanup + if: steps.check-preview.outputs.preview_exists == 'true' + run: mkdir -p empty + + - name: Remove GitHub Pages Build + if: steps.check-preview.outputs.preview_exists == 'true' + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./empty + destination_dir: ui/pr/${{ github.event.pull_request.number }} + keep_files: false + user_name: ${{ github.actor }} + user_email: ${{ github.actor }}@users.noreply.github.com + publish_branch: gh-pages + commit_message: 'chore: Clean up preview for PR #${{ github.event.pull_request.number }}' + + - name: Log Cleanup Completion + run: echo "Cleanup completed for PR \#${{ github.event.pull_request.number }}" + + update-preview-comment: + needs: [cleanup-ui-pr-preview] + name: Update PR Comment + permissions: + pull-requests: write + issues: write + runs-on: ubuntu-latest + steps: + - name: Update PR comment to reflect cleanup + uses: peter-evans/find-comment@v2 + id: find-comment + with: + token: ${{ secrets.GITHUB_TOKEN }} + issue-number: ${{ github.event.pull_request.number }} + body-includes: '' + + - name: Update PR comment to reflect cleanup + if: steps.find-comment.outputs.comment-id != '' + uses: peter-evans/create-or-update-comment@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + comment-id: ${{ steps.find-comment.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + edit-mode: replace + body: | + + 🧹 The live preview for this PR has been removed. + + - name: Log comment update status + run: | + if [ "${{ steps.find-comment.outputs.comment-id }}" != "" ]; then + echo "Updated existing preview comment" + else + echo "No preview comment found to update" + fi diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 5a25b0a3..81e75c22 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -1,7 +1,7 @@ name: Development on: - pull_request_target: + pull_request: types: [opened, synchronize, reopened] jobs: @@ -33,6 +33,11 @@ jobs: with: ref: "${{ github.event.pull_request.merge_commit_sha }}" + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + - name: Install dependencies run: npm ci @@ -67,6 +72,11 @@ jobs: with: ref: "${{ github.event.pull_request.merge_commit_sha }}" + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + - name: Install dependencies run: npm ci @@ -91,7 +101,7 @@ jobs: - name: Run pre-commit checks run: SKIP=ruff-format pre-commit run --all-files - ui-precommit-check: + ui-precommit-checks: permissions: contents: "read" runs-on: ubuntu-latest @@ -101,6 +111,11 @@ jobs: with: ref: "${{ github.event.pull_request.merge_commit_sha }}" + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + - name: Install dependencies run: npm ci @@ -133,6 +148,11 @@ jobs: - name: Check out code uses: actions/checkout@v3 + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + - name: Install dependencies run: npm ci @@ -167,6 +187,11 @@ jobs: with: ref: "${{ github.event.pull_request.merge_commit_sha }}" + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + - name: Install dependencies run: npm ci @@ -224,3 +249,107 @@ jobs: They will be retained for **up to 30 days**. ` }) + + ui-pr-preview: + needs: [ui-quality-checks, ui-precommit-checks, ui-unit-tests, ui-integration-tests] + permissions: + contents: write + pull-requests: write + issues: write + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Check if UI-related files changed + id: check-changes + run: | + BASE_BRANCH=${{ github.event.pull_request.base.ref }} + CHANGED_FILES=$(git diff --name-only origin/$BASE_BRANCH...HEAD) + SHOULD_BUILD=false + + if echo "$CHANGED_FILES" | grep -q "^src/ui/"; then + echo "UI source files changed" + SHOULD_BUILD=true + fi + + echo "should_build=$SHOULD_BUILD" >> $GITHUB_OUTPUT + echo "Should build: $SHOULD_BUILD" + + - name: Install dependencies + if: steps.check-changes.outputs.should_build == 'true' + run: npm ci + + - name: Build app to root + if: steps.check-changes.outputs.should_build == 'true' + id: build + run: | + # Export vars to ensure they are loaded before build + export $(grep -v '^#' .env.development | xargs) + + PR_NUMBER=${{ github.event.pull_request.number }} + echo "pr_number=${PR_NUMBER}" >> $GITHUB_OUTPUT + + # Set asset prefix and base path with PR number + ASSET_PREFIX=https://neuralmagic.github.io/guidellm/ui/pr/${PR_NUMBER} + USE_MOCK_DATA=true + BASE_PATH=/ui/pr/${PR_NUMBER} + GIT_SHA=${{ github.sha }} + export ASSET_PREFIX=${ASSET_PREFIX} + export BASE_PATH=${BASE_PATH} + export GIT_SHA=${GIT_SHA} + export USE_MOCK_DATA=${USE_MOCK_DATA} + npm run build + + - name: Deploy to GitHub Pages + if: steps.check-changes.outputs.should_build == 'true' + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./src/ui/out + destination_dir: ui/pr/${{ steps.build.outputs.pr_number }} + keep_files: false + user_name: ${{ github.actor }} + user_email: ${{ github.actor }}@users.noreply.github.com + publish_branch: gh-pages + commit_message: 'build: Deploy preview build for PR #${{ github.event.pull_request.number }}' + + - name: Set deployment url + if: steps.check-changes.outputs.should_build == 'true' + id: deploy + run: | + DEPLOY_URL=https://neuralmagic.github.io/guidellm/ui/pr/${{ steps.build.outputs.pr_number }} + echo "url=${DEPLOY_URL}" >> $GITHUB_OUTPUT + + - name: Find PR comment + if: steps.check-changes.outputs.should_build == 'true' + uses: peter-evans/find-comment@v2 + id: find-comment + with: + token: ${{ secrets.GITHUB_TOKEN }} + issue-number: ${{ github.event.pull_request.number }} + body-includes: '' + + - name: Post Deployment URL to PR + if: steps.check-changes.outputs.should_build == 'true' + uses: peter-evans/create-or-update-comment@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + comment-id: ${{ steps.find-comment.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + edit-mode: replace + body: | + + 🎉 **Live Preview:** [Click here to view the live version](${{ steps.deploy.outputs.url }}) + *Last updated: ${{ github.sha }}* + + - name: Skip build notification + if: steps.check-changes.outputs.should_build == 'false' + run: echo "Skipping UI preview build - no relevant files changed" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 47aef02e..80c5aaa4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -22,6 +22,25 @@ jobs: - name: Run quality checks run: tox -e quality + ui-quality-checks: + permissions: + contents: "read" + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install dependencies + run: npm ci + + - name: Run quality and typing checks + run: npm run lint + type-checks: runs-on: ubuntu-latest strategy: @@ -38,6 +57,25 @@ jobs: - name: Run quality checks run: tox -e types + ui-type-checks: + permissions: + contents: "read" + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install dependencies + run: npm ci + + - name: Run quality and typing checks + run: npm run type-check + precommit-checks: runs-on: ubuntu-latest strategy: @@ -54,6 +92,25 @@ jobs: - name: Run pre-commit checks run: SKIP=ruff-format pre-commit run --all-files + ui-precommit-checks: + permissions: + contents: "read" + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install dependencies + run: npm ci + + - name: Run pre-commit checks + run: npx husky run pre-commit + unit-tests: runs-on: ubuntu-latest strategy: @@ -70,6 +127,25 @@ jobs: - name: Run unit tests run: tox -e test-unit -- -m "smoke or sanity" + ui-unit-tests: + permissions: + contents: "read" + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install dependencies + run: npm ci + + - name: Run unit tests + run: npm run test:unit + integration-tests: runs-on: ubuntu-latest strategy: @@ -85,3 +161,65 @@ jobs: run: pip install tox - name: Run integration tests run: tox -e test-integration -- -m smoke + + ui-integration-tests: + permissions: + contents: "read" + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install dependencies + run: npm ci + + - name: Run integration tests + run: npm run test:integration + + deploy-ui-build: + needs: [ui-quality-checks, ui-precommit-checks, ui-unit-tests, ui-integration-tests] + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install dependencies + run: npm ci + + - name: Build app to root + id: build + run: | + # Export vars to ensure they are loaded before build + export $(grep -v '^#' .env.development | xargs) + + # Set asset prefix and base path with PR number + ASSET_PREFIX=https://neuralmagic.github.io/guidellm/ui/dev + BASE_PATH=/ui/dev + GIT_SHA=${{ github.sha }} + export ASSET_PREFIX=${ASSET_PREFIX} + export BASE_PATH=${BASE_PATH} + export GIT_SHA=${GIT_SHA} + npm run build + + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./ui/out + destination_dir: ui/dev + keep_files: false + user_name: ${{ github.actor }} + user_email: ${{ github.actor }}@users.noreply.github.com + publish_branch: gh-pages diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 16ee11f9..1bcc286d 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -38,6 +38,25 @@ jobs: - name: Run unit tests run: tox -e test-unit + ui-unit-tests: + permissions: + contents: "read" + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install dependencies + run: npm ci + + - name: Run unit tests + run: npm run test:unit + integration-tests: runs-on: ubuntu-latest strategy: @@ -54,6 +73,25 @@ jobs: - name: Run integration tests run: tox -e test-integration -- -m "smoke or sanity" + ui-integration-tests: + permissions: + contents: "read" + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install dependencies + run: npm ci + + - name: Run integration tests + run: npm run test:integration + e2e-tests: runs-on: ubuntu-latest strategy: @@ -70,6 +108,35 @@ jobs: - name: Run integration tests run: tox -e test-e2e -- -m smoke + ui-e2e-tests: + permissions: + contents: "read" + id-token: "write" + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install dependencies + run: npm ci + + - name: Generate Build + run: | + npm run build + + - name: Start the Next.js app + run: | + npx serve@latest src/ui/out & + npx wait-on http://localhost:3000 # Wait until the app is ready + + - name: Run Cypress tests + run: npm run test:e2e --headless + build-and-publish: needs: [unit-tests, integration-tests, e2e-tests] runs-on: ubuntu-latest @@ -123,3 +190,57 @@ jobs: - name: Log artifact location run: | echo "Artifacts uploaded to: ${{ steps.artifact-upload.outputs.artifact-url }}" + + publish-ui-build: + needs: [ui-unit-tests, ui-integration-tests, ui-e2e-tests] + permissions: + contents: write + pull-requests: write + issues: write + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install dependencies + run: npm ci + + - name: "Set GIT_TAG" + id: vars + run: | + if [ -z "${{ github.ref_name }}" ]; then + echo "TAG=latest" >> $GITHUB_ENV + else + echo "TAG=${{ github.ref_name }}" >> $GITHUB_ENV + fi + + - name: Build app to root + id: build + run: | + # Export vars to ensure they are loaded before build + export $(grep -v '^#' .env.staging | xargs) + + # Set asset prefix and base path with git tag + ASSET_PREFIX=https://neuralmagic.github.io/guidellm/ui/nightly + BASE_PATH=/ui/nightly + GIT_SHA=${{ github.sha }} + export ASSET_PREFIX=${ASSET_PREFIX} + export BASE_PATH=${BASE_PATH} + export GIT_SHA=${GIT_SHA} + npm run build + + - name: Update latest build in GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./ui/out + destination_dir: ui/nightly + keep_files: false + user_name: ${{ github.actor }} + user_email: ${{ github.actor }}@users.noreply.github.com + publish_branch: gh-pages diff --git a/.github/workflows/release-candidate.yml b/.github/workflows/release-candidate.yml index e5a3ceed..e6c2c018 100644 --- a/.github/workflows/release-candidate.yml +++ b/.github/workflows/release-candidate.yml @@ -38,6 +38,25 @@ jobs: - name: Run unit tests run: tox -e test-unit + ui-unit-tests: + permissions: + contents: "read" + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install dependencies + run: npm ci + + - name: Run unit tests + run: npm run test:unit + integration-tests: runs-on: ubuntu-latest strategy: @@ -54,6 +73,25 @@ jobs: - name: Run integration tests run: tox -e test-integration + ui-integration-tests: + permissions: + contents: "read" + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install dependencies + run: npm ci + + - name: Run integration tests + run: npm run test:integration + e2e-tests: runs-on: ubuntu-latest strategy: @@ -70,6 +108,35 @@ jobs: - name: Run end-to-end tests run: tox -e test-e2e + ui-e2e-tests: + permissions: + contents: "read" + id-token: "write" + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install dependencies + run: npm ci + + - name: Generate Build + run: | + npm run build + + - name: Start the Next.js app + run: | + npx serve@latest src/ui/out & + npx wait-on http://localhost:3000 # Wait until the app is ready + + - name: Run Cypress tests + run: npm run test:e2e --headless + build-and-publish: needs: [unit-tests, integration-tests, e2e-tests] runs-on: ubuntu-latest @@ -115,3 +182,109 @@ jobs: username: ${{ secrets.PYPI_PUBLIC_USER }} password: ${{ secrets.PYPI_PUBLIC_AUTH }} whl: $(find dist -name '*.tar.gz') + + publish-versioned-ui-build: + needs: [ui-unit-tests, ui-integration-tests, ui-e2e-tests] + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install dependencies + run: npm ci + + - name: "Set GIT_TAG" + id: vars + run: | + if [ -z "${{ github.ref_name }}" ]; then + echo "TAG=latest" >> $GITHUB_ENV + else + echo "TAG=${{ github.ref_name }}" >> $GITHUB_ENV + fi + + - name: Build app to root + id: build + run: | + # Export vars to ensure they are loaded before build + export $(grep -v '^#' .env.staging | xargs) + + # Set asset prefix and base path with git tag + ASSET_PREFIX=https://neuralmagic.github.io/guidellm/ui/release/${TAG} + BASE_PATH=/ui/release/${TAG} + GIT_SHA=${{ github.sha }} + export ASSET_PREFIX=${ASSET_PREFIX} + export BASE_PATH=${BASE_PATH} + export GIT_SHA=${GIT_SHA} + npm run build + + - name: Deploy versioned build to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./ui/out + destination_dir: ui/release/${TAG} + keep_files: false + user_name: ${{ github.actor }} + user_email: ${{ github.actor }}@users.noreply.github.com + publish_branch: gh-pages + + publish-latest-ui-build: + needs: [publish-versioned-ui-build] + permissions: + contents: write + pull-requests: write + issues: write + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install dependencies + run: npm ci + + - name: "Set GIT_TAG" + id: vars + run: | + if [ -z "${{ github.ref_name }}" ]; then + echo "TAG=latest" >> $GITHUB_ENV + else + echo "TAG=${{ github.ref_name }}" >> $GITHUB_ENV + fi + + - name: Build app to root + id: build + run: | + # Export vars to ensure they are loaded before build + export $(grep -v '^#' .env.staging | xargs) + + # Set asset prefix and base path with git tag + ASSET_PREFIX=https://neuralmagic.github.io/guidellm/release/latest + BASE_PATH=/release/latest + GIT_SHA=${{ github.sha }} + export ASSET_PREFIX=${ASSET_PREFIX} + export BASE_PATH=${BASE_PATH} + export GIT_SHA=${GIT_SHA} + npm run build + + - name: Update latest build in GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./ui/out + destination_dir: ui/release/latest + keep_files: false + user_name: ${{ github.actor }} + user_email: ${{ github.actor }}@users.noreply.github.com + publish_branch: gh-pages diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5499b525..82b4306e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,7 +3,7 @@ name: Release on: push: tags: - - 'v*.*.*' + - "v*.*.*" jobs: build-and-publish: @@ -37,7 +37,7 @@ jobs: retention-days: 90 - name: Log artifact location run: | - echo "Artifacts uploaded to: Artifacts uploaded to: ${{ steps.artifact-upload.outputs.artifact-url }}" + echo "Artifacts uploaded to: ${{ steps.artifact-upload.outputs.artifact-url }}" - name: Push wheel to PyPI uses: neuralmagic/nm-actions/actions/publish-whl@v1.0.0 with: @@ -83,6 +83,25 @@ jobs: - name: Run unit tests run: tox -e test-unit + ui-unit-tests: + permissions: + contents: "read" + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install dependencies + run: npm ci + + - name: Run unit tests + run: npm run test:unit + integration-tests: runs-on: ubuntu-latest strategy: @@ -99,6 +118,25 @@ jobs: - name: Run integration tests run: tox -e test-integration + ui-integration-tests: + permissions: + contents: "read" + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install dependencies + run: npm ci + + - name: Run integration tests + run: npm run test:integration + e2e-tests: runs-on: ubuntu-latest strategy: @@ -114,3 +152,138 @@ jobs: run: pip install tox - name: Run end-to-end tests run: tox -e test-e2e + + ui-e2e-tests: + permissions: + contents: "read" + id-token: "write" + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install dependencies + run: npm ci + + - name: Generate Build + run: | + npm run build + + - name: Start the Next.js app + run: | + npx serve@latest src/ui/out & + npx wait-on http://localhost:3000 # Wait until the app is ready + + - name: Run Cypress tests + run: npm run test:e2e --headless + + publish-versioned-ui-build: + needs: [ui-unit-tests, ui-integration-tests, ui-e2e-tests] + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install dependencies + run: npm ci + + - name: "Set GIT_TAG" + id: vars + run: | + if [ -z "${{ github.ref_name }}" ]; then + echo "TAG=latest" >> $GITHUB_ENV + else + echo "TAG=${{ github.ref_name }}" >> $GITHUB_ENV + fi + + - name: Build app to root + id: build + run: | + # Export vars to ensure they are loaded before build + export $(grep -v '^#' .env.production | xargs) + + # Set asset prefix and base path with git tag + ASSET_PREFIX=https://neuralmagic.github.io/guidellm/ui/${TAG} + BASE_PATH=/ui/${TAG} + GIT_SHA=${{ github.sha }} + export ASSET_PREFIX=${ASSET_PREFIX} + export BASE_PATH=${BASE_PATH} + export GIT_SHA=${GIT_SHA} + npm run build + + - name: Deploy versioned build to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./ui/out + destination_dir: ui/${TAG} + keep_files: false + user_name: ${{ github.actor }} + user_email: ${{ github.actor }}@users.noreply.github.com + publish_branch: gh-pages + + publish-latest-ui-build: + needs: [publish-versioned-ui-build] + permissions: + contents: write + pull-requests: write + issues: write + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install dependencies + run: npm ci + + - name: "Set GIT_TAG" + id: vars + run: | + if [ -z "${{ github.ref_name }}" ]; then + echo "TAG=latest" >> $GITHUB_ENV + else + echo "TAG=${{ github.ref_name }}" >> $GITHUB_ENV + fi + + - name: Build app to root + id: build + run: | + # Export vars to ensure they are loaded before build + export $(grep -v '^#' .env.production | xargs) + + # Set asset prefix and base path with git tag + ASSET_PREFIX=https://neuralmagic.github.io/guidellm/ui/latest + BASE_PATH=/ui/latest + GIT_SHA=${{ github.sha }} + export ASSET_PREFIX=${ASSET_PREFIX} + export BASE_PATH=${BASE_PATH} + export GIT_SHA=${GIT_SHA} + npm run build + + - name: Update latest build in GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./ui/out + destination_dir: ui/latest + keep_files: false + user_name: ${{ github.actor }} + user_email: ${{ github.actor }}@users.noreply.github.com + publish_branch: gh-pages diff --git a/.gitignore b/.gitignore index ebbf9b09..dccc8bd8 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ src/guidellm/version.py benchmarks.json benchmarks.yaml benchmarks.csv +benchmarks.html # Byte-compiled / optimized / DLL files __pycache__/ @@ -227,4 +228,5 @@ src/ui/next-env.d.ts !tsconfig.*.json !src/ui/lib !src/ui/public/manifest.json +!src/ui/serve.json .eslintcache diff --git a/DEVELOPING.md b/DEVELOPING.md index dde51744..517ce689 100644 --- a/DEVELOPING.md +++ b/DEVELOPING.md @@ -267,6 +267,16 @@ Reference [https://www.npmjs.com/package/jest-runner-groups](jest-runner-groups) */ ``` +### 🧪 UI Development Notes + +During development, it can be helpful to view sample data. We include a sample benchmark run wired into the Redux store under: + +``` +src/ui/lib/store/[runInfo/workloadDetails/benchmarks]WindowData.ts +``` + +In the future this will be replaced by a configurable untracked file for dev use. + ## Additional Resources - [CONTRIBUTING.md](https://github.com/neuralmagic/guidellm/blob/main/CONTRIBUTING.md): Guidelines for contributing to the project. diff --git a/README.md b/README.md index 1e489bb5..a895bcd5 100644 --- a/README.md +++ b/README.md @@ -157,17 +157,19 @@ The `guidellm benchmark` command is used to run benchmarks against a generative GuideLLM UI is a companion frontend for visualizing the results of a GuideLLM benchmark run. -### 🛠Running the UI +### 🛠Generating an HTML report with a benchmark run -1. Use the Hosted Build (Recommended for Most Users) - -After running a benchmark with GuideLLM, a report.html file will be generated (by default at guidellm_report/report.html). This file references the latest stable version of the UI hosted at: +Set the output to benchmarks.html for your run: +```base +--output-path=benchmarks.html ``` -https://neuralmagic.github.io/guidellm/ui/dev/ -``` -Open the file in your browser and you're done—no setup required. +1. Use the Hosted Build (Recommended for Most Users) + +This is preconfigured. The latest stable version of the hosted UI (https://neuralmagic.github.io/guidellm/ui/latest) will be used to build the local html file. + +Open benchmarks.html in your browser and you're done—no setup required. 2. Build and Serve the UI Locally (For Development) This option is useful if: @@ -180,20 +182,16 @@ Open the file in your browser and you're done—no setup required. ```bash npm install npm run build -npx serve out +npm run serve ``` -This will start a local server (e.g., at http://localhost:3000). Then, in your GuideLLM config or CLI flags, point to this local server as the asset base for report generation. - -### 🧪 Development Notes +This will start a local server (e.g., at http://localhost:3000). Then set the Environment to LOCAL before running your benchmarks. -During UI development, it can be helpful to view sample data. We include a sample benchmark run wired into the Redux store under: +```bash +export GUIDELLM__ENV=local +Alternatively, in config.py update the ENV_REPORT_MAPPING used as the asset base for report generation to the LOCAL option. ``` -src/lib/store/[runInfo/workloadDetails/benchmarks]WindowData.ts -``` - -In the future this will be replaced by a configurable untracked file for dev use. ## Resources diff --git a/eslint.config.js b/eslint.config.js index 178aaccb..bdb830d3 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,76 +1,72 @@ // @ts-check import eslint from '@eslint/js'; -import typescriptPlugin from '@typescript-eslint/eslint-plugin'; -import typescriptParser from '@typescript-eslint/parser'; -import { FlatCompat } from '@eslint/eslintrc'; -import reactPlugin from 'eslint-plugin-react'; -import hooksPlugin from 'eslint-plugin-react-hooks'; +import nextPlugin from '@next/eslint-plugin-next'; +import prettierConfig from 'eslint-config-prettier'; +import cypressPlugin from 'eslint-plugin-cypress'; import importPlugin from 'eslint-plugin-import'; import jestPlugin from 'eslint-plugin-jest'; -import noSecretsPlugin from 'eslint-plugin-no-secrets'; import prettierPlugin from 'eslint-plugin-prettier'; -import prettierConfig from 'eslint-config-prettier'; +import reactPlugin from 'eslint-plugin-react'; +import hooksPlugin from 'eslint-plugin-react-hooks'; import globals from 'globals'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; +import tseslint from 'typescript-eslint'; +// --- SETUP --- +// Recreate __dirname for ES modules const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: eslint.configs.recommended, -}); +// --- EXPORT ESLINT CONFIG --- +export default tseslint.config( + // 1. Global Ignores + { + ignores: ['node_modules/', '.next/', 'dist/', 'coverage/', '.DS_Store'], + }, -export default [ - // Base configuration + // 2. Base Configurations (Applied to all files) eslint.configs.recommended, + prettierConfig, // Disables ESLint rules that conflict with Prettier. IMPORTANT: Must be after other configs. - // Next.js configuration using FlatCompat - ...compat.extends('next/core-web-vitals'), - - // --- Main Configuration --- + // 3. Configuration for App Source Code (Next.js with Type-Aware Linting) { - files: ['src/**/*.{js,jsx,ts,tsx}', 'tests/**/*.{js,jsx,ts,tsx}'], + files: ['src/ui/**/*.{ts,tsx}'], languageOptions: { - parser: typescriptParser, - ecmaVersion: 2024, - sourceType: 'module', + parser: tseslint.parser, + parserOptions: { + project: true, // Enable type-aware linting + tsconfigRootDir: __dirname, + }, globals: { ...globals.browser, - ...globals.node, - ...globals.jest, - }, - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - project: [ - './src/ui/tsconfig.json', - './tsconfig.test.json', - './tsconfig.cypress.json', - ], - tsconfigRootDir: import.meta.dirname, - noWarnOnMultipleProjects: true, + ...globals.node, // Add Node.js globals for `process` etc. }, }, plugins: { - '@typescript-eslint': typescriptPlugin, + '@typescript-eslint': tseslint.plugin, + '@next/next': nextPlugin, + import: importPlugin, react: reactPlugin, 'react-hooks': hooksPlugin, - import: importPlugin, - jest: jestPlugin, - 'no-secrets': noSecretsPlugin, prettier: prettierPlugin, }, rules: { - // Ccustom rules - complexity: ['warn', { max: 8 }], - curly: ['error', 'all'], + // --- Base rules to disable in favor of TS versions --- 'no-unused-vars': 'off', - // TypeScript rules + // --- Recommended rules from plugins --- + ...tseslint.configs.recommendedTypeChecked.rules, + ...nextPlugin.configs.recommended.rules, + ...nextPlugin.configs['core-web-vitals'].rules, + ...reactPlugin.configs.recommended.rules, + ...hooksPlugin.configs.recommended.rules, + + // --- Prettier --- + 'prettier/prettier': 'error', + + // --- Custom Rules & Overrides --- '@typescript-eslint/no-unused-vars': [ 'warn', { @@ -79,103 +75,103 @@ export default [ caughtErrorsIgnorePattern: '^_', }, ], + '@typescript-eslint/no-floating-promises': 'error', + '@typescript-eslint/no-misused-promises': 'error', '@typescript-eslint/no-explicit-any': 'warn', - // Next.js overrides - '@next/next/no-img-element': 'off', // Allow img tags if needed - '@next/next/no-page-custom-font': 'warn', - - // React rules - 'react/react-in-jsx-scope': 'off', // Not needed in Next.js - 'react/prop-types': 'off', // Using TypeScript - 'react-hooks/rules-of-hooks': 'error', - 'react-hooks/exhaustive-deps': 'warn', - - // Import rules - 'import/no-extraneous-dependencies': [ - 'error', - { - devDependencies: [ - '**/*.test.{js,jsx,ts,tsx}', - '**/*.d.ts', - '**/*.interfaces.ts', - '**/*.setup.{js,ts}', - '**/*.config.{js,mjs,ts}', - 'tests/**/*', - 'cypress/**/*', - ], - optionalDependencies: false, - peerDependencies: false, - }, - ], 'import/order': [ 'error', { - groups: [ - ['builtin', 'external'], - ['internal', 'parent', 'sibling', 'index'], - ], - 'newlines-between': 'always-and-inside-groups', - pathGroups: [ - { - pattern: - '@{app,assets,classes,components,hooks,lib,pages,store,tests,types,utils}/**', - group: 'internal', - position: 'before', - }, - { - pattern: '{.,..}/**', - group: 'internal', - position: 'after', - }, - ], - pathGroupsExcludedImportTypes: ['builtin'], + groups: [['builtin', 'external'], 'internal', ['parent', 'sibling', 'index']], + 'newlines-between': 'always', alphabetize: { order: 'asc', caseInsensitive: true }, }, ], - // Security - 'no-secrets/no-secrets': ['error', { additionalRegexes: {}, ignoreContent: [] }], + 'react/react-in-jsx-scope': 'off', + 'react/prop-types': 'off', - // Prettier - 'prettier/prettier': 'error', + '@next/next/no-html-link-for-pages': 'off', + '@next/next/no-img-element': 'off', + + complexity: ['warn', { max: 8 }], }, settings: { - next: { - rootDir: ['src/ui/', 'tests/ui/'], - }, - 'import/resolver': { - typescript: { - project: [ - './src/ui/tsconfig.json', - './tsconfig.test.json', - './tsconfig.cypress.json', - ], - noWarnOnMultipleProjects: true, - }, + react: { version: 'detect' }, + 'import/resolver': { typescript: true, node: true }, + }, + }, + + // 4. Configuration for Jest Test Files (Type-Aware) + { + files: ['tests/ui/**/*.{test,spec}.{ts,tsx}', 'jest.setup.ts'], + languageOptions: { + parser: tseslint.parser, // Explicitly set parser + parserOptions: { + project: './tsconfig.test.json', + tsconfigRootDir: __dirname, }, - react: { - version: 'detect', + globals: { + ...globals.jest, + ...globals.node, // FIX: Add Node.js globals for `global`, etc. }, }, + plugins: { + jest: jestPlugin, + }, + rules: { + ...jestPlugin.configs['flat/recommended'].rules, + '@typescript-eslint/unbound-method': 'off', + }, }, - // Jest-specific rules for test files + // 5. Configuration for Cypress E2E Test Files (Type-Aware) { files: [ - 'tests/**/*.{js,jsx,ts,tsx}', - '**/*.test.{js,jsx,ts,tsx}', - '**/*.spec.{js,jsx,ts,tsx}', + 'tests/ui/cypress/**/*.{cy,e2e}.{ts,tsx}', + 'tests/ui/cypress/support/**/*.ts', ], + languageOptions: { + parser: tseslint.parser, // Explicitly set parser + parserOptions: { + project: './tsconfig.cypress.json', + tsconfigRootDir: __dirname, + }, + // FIX: This is the correct way to get globals from the Cypress plugin's recommended config. + globals: cypressPlugin.configs.recommended.languageOptions.globals, + }, + plugins: { + cypress: cypressPlugin, + }, + // Apply recommended rules and then add our overrides rules: { - 'jest/expect-expect': 'error', - 'jest/no-focused-tests': 'error', - 'jest/no-identical-title': 'error', - 'jest/prefer-to-have-length': 'warn', - 'jest/valid-expect': 'error', + ...cypressPlugin.configs.recommended.rules, + 'jest/expect-expect': 'off', + 'jest/no-standalone-expect': 'off', + '@typescript-eslint/no-floating-promises': 'off', }, }, - // Prettier config (disables conflicting rules) - prettierConfig, -]; + // 6. Configuration for JS/TS config files + { + files: ['**/*.config.{js,mjs,ts}'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + rules: { + '@typescript-eslint/no-var-requires': 'off', + }, + }, + + // 7. Configuration for JS/TS mock files and test helpers + { + files: ['tests/ui/**/__mocks__/**/*.{js,ts}', 'tests/ui/unit/mocks/**/*.ts'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + } +); diff --git a/jest.setup.ts b/jest.setup.ts index eb162bb7..24fe6072 100644 --- a/jest.setup.ts +++ b/jest.setup.ts @@ -11,7 +11,6 @@ jest.mock('next/dynamic', () => ({ const dynamicModule = jest.requireActual('next/dynamic'); const dynamicActualComp = dynamicModule.default; const RequiredComponent = dynamicActualComp(props[0]); - // eslint-disable-next-line no-unused-expressions, @typescript-eslint/no-unused-expressions RequiredComponent.preload ? RequiredComponent.preload() : RequiredComponent.render.preload(); diff --git a/package-lock.json b/package-lock.json index 7bf63e9a..15034bb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,14 +39,13 @@ "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", "@types/testing-library__jest-dom": "^5.14.9", - "@typescript-eslint/eslint-plugin": "^8.33.1", - "@typescript-eslint/parser": "^8.33.1", "cross-fetch": "^4.1.0", "cypress": "^13.13.3", "eslint": "^9.0.0", "eslint-config-next": "15.3.2", "eslint-config-prettier": "^8.5.0", "eslint-import-resolver-typescript": "^4.4.2", + "eslint-plugin-cypress": "^5.1.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-jest": "^28.11.0", "eslint-plugin-jsx-a11y": "^6.6.1", @@ -62,14 +61,13 @@ "jest-runner-groups": "^2.2.0", "jest-transform-stub": "^2.0.0", "prettier": "^3.5.3", - "typescript": "^5" + "serve": "^14.2.4", + "sharp": "^0.32.0", + "typescript": "^5", + "typescript-eslint": "^8.34.0" }, "engines": { "node": ">=22" - }, - "optionalDependencies": { - "@next/swc-linux-x64-gnu": "^15.3.3", - "@next/swc-linux-x64-musl": "^15.3.3" } }, "node_modules/@adobe/css-tools": { @@ -2041,6 +2039,16 @@ "ms": "^2.1.1" } }, + "node_modules/@emnapi/runtime": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", + "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emotion/babel-plugin": { "version": "11.13.5", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", @@ -2416,6 +2424,28 @@ "@img/sharp-libvips-darwin-arm64": "1.1.0" } }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.2.tgz", + "integrity": "sha512-dYvWqmjU9VxqXmjEtjmvHnGqF8GrVjM2Epj9rJ6BUIXvk8slvNDJbhGFvIoXzkDhrJC2jUxNLz/GUjjvSzfw+g==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.1.0" + } + }, "node_modules/@img/sharp-libvips-darwin-arm64": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.1.0.tgz", @@ -2432,131 +2462,467 @@ "url": "https://opencollective.com/libvips" } }, - "node_modules/@istanbuljs/load-nyc-config": { + "node_modules/@img/sharp-libvips-darwin-x64": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.1.0.tgz", + "integrity": "sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.1.0.tgz", + "integrity": "sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.1.0.tgz", + "integrity": "sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.1.0.tgz", + "integrity": "sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.1.0.tgz", + "integrity": "sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.1.0.tgz", + "integrity": "sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.1.0.tgz", + "integrity": "sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/libvips" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.1.0.tgz", + "integrity": "sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.2.tgz", + "integrity": "sha512-0DZzkvuEOqQUP9mo2kjjKNok5AmnOr1jB2XYjkaoNRwpAYMDzRmAqUIa1nRi58S2WswqSfPOWLNOr0FDT3H5RQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.1.0" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.2.tgz", + "integrity": "sha512-D8n8wgWmPDakc83LORcfJepdOSN6MvWNzzz2ux0MnIbOqdieRZwVYY32zxVx+IFUT8er5KPcyU3XXsn+GzG/0Q==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.1.0" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.2.tgz", + "integrity": "sha512-EGZ1xwhBI7dNISwxjChqBGELCWMGDvmxZXKjQRuqMrakhO8QoMgqCrdjnAqJq/CScxfRn+Bb7suXBElKQpPDiw==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.1.0" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.2.tgz", + "integrity": "sha512-sD7J+h5nFLMMmOXYH4DD9UtSNBD05tWSSdWAcEyzqW8Cn5UxXvsHAxmxSesYUsTOBmUnjtxghKDl15EvfqLFbQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.1.0" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.2.tgz", + "integrity": "sha512-NEE2vQ6wcxYav1/A22OOxoSOGiKnNmDzCYFOZ949xFmrWZOVII1Bp3NqVVpvj+3UeHMFyN5eP/V5hzViQ5CZNA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.1.0" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.2.tgz", + "integrity": "sha512-DOYMrDm5E6/8bm/yQLCWyuDJwUnlevR8xtF8bs+gjZ7cyUNYXiSf/E8Kp0Ss5xasIaXSHzb888V1BE4i1hFhAA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.1.0" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.2.tgz", + "integrity": "sha512-/VI4mdlJ9zkaq53MbIG6rZY+QRN3MLbR6usYlgITEzi4Rpx5S6LFKsycOQjkOGmqTNmkIdLjEvooFKwww6OpdQ==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.4.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.2.tgz", + "integrity": "sha512-cfP/r9FdS63VA5k0xiqaNaEoGxBg9k7uE+RQGzuK9fHt7jib4zAVVseR9LsE4gJcNWgT6APKMNnCcnyOtmSEUQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.2.tgz", + "integrity": "sha512-QLjGGvAbj0X/FXl8n1WbtQ6iVBpWU7JO94u/P2M4a8CFYsvQi4GW2mRy/JqkRx0qpBzaOdKJKw8uc930EX2AHw==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.2.tgz", + "integrity": "sha512-aUdT6zEYtDKCaxkofmmJDJYGCf0+pJg3eU9/oBuqvEeoB9dKI6ZLc/1iLJCTuJQDO4ptntAlkUmHgGjyuobZbw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" } }, "node_modules/@jest/console": { @@ -3369,38 +3735,6 @@ "node": ">= 10" } }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.3.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.3.tgz", - "integrity": "sha512-jJ8HRiF3N8Zw6hGlytCj5BiHyG/K+fnTKVDEKvUCyiQ/0r5tgwO7OgaRiOjjRoIx2vwLR+Rz8hQoPrnmFbJdfw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "15.3.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.3.tgz", - "integrity": "sha512-HrUcTr4N+RgiiGn3jjeT6Oo208UT/7BuTr7K0mdKRBtTbT4v9zJqCDKO97DUqqoBK1qyzP1RwvrWTvU6EPh/Cw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@next/swc-win32-arm64-msvc": { "version": "15.3.2", "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.2.tgz", @@ -4572,17 +4906,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.0.tgz", - "integrity": "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==", + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.1.tgz", + "integrity": "sha512-9XNTlo7P7RJxbVeICaIIIEipqxLKguyh+3UbXuT2XQuFp6d8VOeDEGuz5IiX0dgZo8CiI6aOFLg4e8cF71SFVg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.34.0", - "@typescript-eslint/type-utils": "8.34.0", - "@typescript-eslint/utils": "8.34.0", - "@typescript-eslint/visitor-keys": "8.34.0", + "@typescript-eslint/scope-manager": "8.35.1", + "@typescript-eslint/type-utils": "8.35.1", + "@typescript-eslint/utils": "8.35.1", + "@typescript-eslint/visitor-keys": "8.35.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -4596,7 +4930,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.34.0", + "@typescript-eslint/parser": "^8.35.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } @@ -4612,16 +4946,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.0.tgz", - "integrity": "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA==", + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.1.tgz", + "integrity": "sha512-3MyiDfrfLeK06bi/g9DqJxP5pV74LNv4rFTyvGDmT3x2p1yp1lOd+qYZfiRPIOf/oON+WRZR5wxxuF85qOar+w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.34.0", - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/typescript-estree": "8.34.0", - "@typescript-eslint/visitor-keys": "8.34.0", + "@typescript-eslint/scope-manager": "8.35.1", + "@typescript-eslint/types": "8.35.1", + "@typescript-eslint/typescript-estree": "8.35.1", + "@typescript-eslint/visitor-keys": "8.35.1", "debug": "^4.3.4" }, "engines": { @@ -4637,14 +4971,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.0.tgz", - "integrity": "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw==", + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.1.tgz", + "integrity": "sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.34.0", - "@typescript-eslint/types": "^8.34.0", + "@typescript-eslint/tsconfig-utils": "^8.35.1", + "@typescript-eslint/types": "^8.35.1", "debug": "^4.3.4" }, "engines": { @@ -4659,14 +4993,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.0.tgz", - "integrity": "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw==", + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.1.tgz", + "integrity": "sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/visitor-keys": "8.34.0" + "@typescript-eslint/types": "8.35.1", + "@typescript-eslint/visitor-keys": "8.35.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4677,9 +5011,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.0.tgz", - "integrity": "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA==", + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.1.tgz", + "integrity": "sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ==", "dev": true, "license": "MIT", "engines": { @@ -4694,14 +5028,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.0.tgz", - "integrity": "sha512-n7zSmOcUVhcRYC75W2pnPpbO1iwhJY3NLoHEtbJwJSNlVAZuwqu05zY3f3s2SDWWDSo9FdN5szqc73DCtDObAg==", + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.1.tgz", + "integrity": "sha512-HOrUBlfVRz5W2LIKpXzZoy6VTZzMu2n8q9C2V/cFngIC5U1nStJgv0tMV4sZPzdf4wQm9/ToWUFPMN9Vq9VJQQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.34.0", - "@typescript-eslint/utils": "8.34.0", + "@typescript-eslint/typescript-estree": "8.35.1", + "@typescript-eslint/utils": "8.35.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -4718,9 +5052,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.0.tgz", - "integrity": "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA==", + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.1.tgz", + "integrity": "sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ==", "dev": true, "license": "MIT", "engines": { @@ -4732,16 +5066,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.0.tgz", - "integrity": "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg==", + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.1.tgz", + "integrity": "sha512-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.34.0", - "@typescript-eslint/tsconfig-utils": "8.34.0", - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/visitor-keys": "8.34.0", + "@typescript-eslint/project-service": "8.35.1", + "@typescript-eslint/tsconfig-utils": "8.35.1", + "@typescript-eslint/types": "8.35.1", + "@typescript-eslint/visitor-keys": "8.35.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -4761,9 +5095,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4800,16 +5134,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.0.tgz", - "integrity": "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ==", + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.1.tgz", + "integrity": "sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.34.0", - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/typescript-estree": "8.34.0" + "@typescript-eslint/scope-manager": "8.35.1", + "@typescript-eslint/types": "8.35.1", + "@typescript-eslint/typescript-estree": "8.35.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4824,14 +5158,14 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.0.tgz", - "integrity": "sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA==", + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.1.tgz", + "integrity": "sha512-VRwixir4zBWCSTP/ljEo091lbpypz57PoeAQ9imjG+vbeof9LplljsL1mos4ccG6H9IjfrVGM359RozUnuFhpw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.34.0", - "eslint-visitor-keys": "^4.2.0" + "@typescript-eslint/types": "8.35.1", + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4868,6 +5202,13 @@ "darwin" ] }, + "node_modules/@zeit/schemas": { + "version": "2.36.0", + "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz", + "integrity": "sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==", + "dev": true, + "license": "MIT" + }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", @@ -4876,6 +5217,20 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -4967,6 +5322,16 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -5054,6 +5419,13 @@ ], "license": "MIT" }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -5355,6 +5727,13 @@ "node": ">= 0.4" } }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -5541,16 +5920,93 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bare-events": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz", + "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", + "dev": true, + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.1.5.tgz", + "integrity": "sha512-1zccWBMypln0jEE05LzZt+V/8y8AQsQQqxtklqaIyg5nu6OAYFhZxPXinJTSG+kU5qyNmeLgcn9AW7eHiCHVLA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.1.tgz", + "integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.5.tgz", + "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "streamx": "^2.21.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -5582,6 +6038,18 @@ "tweetnacl": "^0.14.3" } }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/blob-util": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", @@ -5603,6 +6071,146 @@ "dev": true, "license": "ISC" }, + "node_modules/boxen": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz", + "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.0", + "chalk": "^5.0.1", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/boxen/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/boxen/node_modules/camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/boxen/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==", + "dev": true, + "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/boxen/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/boxen/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/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==", + "dev": true, + "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/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -5723,6 +6331,16 @@ "node": ">=10.16.0" } }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/cachedir": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", @@ -5849,6 +6467,22 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chalk-template": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", + "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, "node_modules/char-regex": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", @@ -5869,6 +6503,13 @@ "node": ">= 0.8.0" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC" + }, "node_modules/ci-info": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.2.0.tgz", @@ -5902,6 +6543,19 @@ "node": ">=6" } }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -5954,6 +6608,71 @@ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/clipboardy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz", + "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "arch": "^2.2.0", + "execa": "^5.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/clipboardy/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -6000,8 +6719,8 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" @@ -6034,8 +6753,8 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" @@ -6081,6 +6800,62 @@ "node": ">=4.0.0" } }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/compression/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==", + "dev": true, + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -6088,6 +6863,16 @@ "dev": true, "license": "MIT" }, + "node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", @@ -6645,6 +7430,22 @@ "dev": true, "license": "MIT" }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/dedent": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", @@ -6660,6 +7461,16 @@ } } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -6746,8 +7557,8 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "devOptional": true, "license": "Apache-2.0", - "optional": true, "engines": { "node": ">=8" } @@ -6901,6 +7712,13 @@ "node": ">= 0.4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -7504,6 +8322,19 @@ "ms": "^2.1.1" } }, + "node_modules/eslint-plugin-cypress": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-5.1.0.tgz", + "integrity": "sha512-tdLXm4aq9vX2hTtKJTUFD3gdNseMKqsf8+P6hI4TtOPdz1LU4xvTpQBd1++qPAsPZP2lyYh71B5mvzu2lBr4Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "globals": "^16.2.0" + }, + "peerDependencies": { + "eslint": ">=9" + } + }, "node_modules/eslint-plugin-import": { "version": "2.31.0", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", @@ -7910,6 +8741,16 @@ "node": ">= 0.8.0" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, "node_modules/expect": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", @@ -8056,6 +8897,13 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -8324,6 +9172,13 @@ "tslib": "^2.1.0" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT" + }, "node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -8538,6 +9393,13 @@ "assert-plus": "^1.0.0" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true, + "license": "MIT" + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -9176,6 +10038,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -9334,6 +10212,19 @@ "node": ">=8" } }, + "node_modules/is-port-reachable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz", + "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -9519,6 +10410,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -11321,6 +12225,19 @@ "node": ">=6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -11368,6 +12285,13 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "license": "MIT" + }, "node_modules/mkdirp/node_modules/minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", @@ -11399,6 +12323,13 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "dev": true, + "license": "MIT" + }, "node_modules/napi-postinstall": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.2.4.tgz", @@ -11422,6 +12353,16 @@ "dev": true, "license": "MIT" }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/next": { "version": "15.3.2", "resolved": "https://registry.npmjs.org/next/-/next-15.3.2.tgz", @@ -11508,6 +12449,61 @@ "node": ">= 10" } }, + "node_modules/next/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/next/node_modules/sharp": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.2.tgz", + "integrity": "sha512-lszvBmB9QURERtyKT2bNmsgxXK0ShJrL/fvqlonCo7e6xBF8nT8xU6pW+PMIbLsz0RxQk3rgH9kd8UmvOzlMJg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.4", + "semver": "^7.7.2" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.2", + "@img/sharp-darwin-x64": "0.34.2", + "@img/sharp-libvips-darwin-arm64": "1.1.0", + "@img/sharp-libvips-darwin-x64": "1.1.0", + "@img/sharp-libvips-linux-arm": "1.1.0", + "@img/sharp-libvips-linux-arm64": "1.1.0", + "@img/sharp-libvips-linux-ppc64": "1.1.0", + "@img/sharp-libvips-linux-s390x": "1.1.0", + "@img/sharp-libvips-linux-x64": "1.1.0", + "@img/sharp-libvips-linuxmusl-arm64": "1.1.0", + "@img/sharp-libvips-linuxmusl-x64": "1.1.0", + "@img/sharp-linux-arm": "0.34.2", + "@img/sharp-linux-arm64": "0.34.2", + "@img/sharp-linux-s390x": "0.34.2", + "@img/sharp-linux-x64": "0.34.2", + "@img/sharp-linuxmusl-arm64": "0.34.2", + "@img/sharp-linuxmusl-x64": "0.34.2", + "@img/sharp-wasm32": "0.34.2", + "@img/sharp-win32-arm64": "0.34.2", + "@img/sharp-win32-ia32": "0.34.2", + "@img/sharp-win32-x64": "0.34.2" + } + }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -11519,6 +12515,39 @@ "tslib": "^2.0.3" } }, + "node_modules/node-abi": { + "version": "3.75.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz", + "integrity": "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "dev": true, + "license": "MIT" + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -11744,6 +12773,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -11947,6 +12986,13 @@ "node": ">=0.10.0" } }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -11963,6 +13009,13 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "license": "MIT" }, + "node_modules/path-to-regexp": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "dev": true, + "license": "MIT" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -12144,6 +13197,63 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prebuild-install/node_modules/tar-fs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", + "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/prebuild-install/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -12374,6 +13484,49 @@ ], "license": "MIT" }, + "node_modules/range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -12467,6 +13620,21 @@ "react-dom": ">=16.6.0" } }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -12567,15 +13735,39 @@ "dev": true, "license": "MIT", "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.0", - "regjsgen": "^0.8.0", - "regjsparser": "^0.12.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/registry-auth-token": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "^1.0.1" }, "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, "node_modules/regjsgen": { @@ -12631,6 +13823,16 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -12903,6 +14105,108 @@ "semver": "bin/semver.js" } }, + "node_modules/serve": { + "version": "14.2.4", + "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.4.tgz", + "integrity": "sha512-qy1S34PJ/fcY8gjVGszDB3EXiPSk5FKhUa7tQe0UPRddxRidc2V6cNHPNewbE1D7MAkgLuWEt3Vw56vYy73tzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zeit/schemas": "2.36.0", + "ajv": "8.12.0", + "arg": "5.0.2", + "boxen": "7.0.0", + "chalk": "5.0.1", + "chalk-template": "0.4.0", + "clipboardy": "3.0.0", + "compression": "1.7.4", + "is-port-reachable": "4.0.0", + "serve-handler": "6.1.6", + "update-check": "1.5.4" + }, + "bin": { + "serve": "build/main.js" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/serve-handler": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", + "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "mime-types": "2.1.18", + "minimatch": "3.1.2", + "path-is-inside": "1.0.2", + "path-to-regexp": "3.3.0", + "range-parser": "1.2.0" + } + }, + "node_modules/serve-handler/node_modules/mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-handler/node_modules/mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "~1.33.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/serve/node_modules/chalk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", + "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/serve/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==", + "dev": true, + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -12953,53 +14257,35 @@ } }, "node_modules/sharp": { - "version": "0.34.2", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.2.tgz", - "integrity": "sha512-lszvBmB9QURERtyKT2bNmsgxXK0ShJrL/fvqlonCo7e6xBF8nT8xU6pW+PMIbLsz0RxQk3rgH9kd8UmvOzlMJg==", + "version": "0.32.6", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz", + "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==", + "dev": true, "hasInstallScript": true, "license": "Apache-2.0", - "optional": true, "dependencies": { "color": "^4.2.3", - "detect-libc": "^2.0.4", - "semver": "^7.7.2" + "detect-libc": "^2.0.2", + "node-addon-api": "^6.1.0", + "prebuild-install": "^7.1.1", + "semver": "^7.5.4", + "simple-get": "^4.0.1", + "tar-fs": "^3.0.4", + "tunnel-agent": "^0.6.0" }, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=14.15.0" }, "funding": { "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.34.2", - "@img/sharp-darwin-x64": "0.34.2", - "@img/sharp-libvips-darwin-arm64": "1.1.0", - "@img/sharp-libvips-darwin-x64": "1.1.0", - "@img/sharp-libvips-linux-arm": "1.1.0", - "@img/sharp-libvips-linux-arm64": "1.1.0", - "@img/sharp-libvips-linux-ppc64": "1.1.0", - "@img/sharp-libvips-linux-s390x": "1.1.0", - "@img/sharp-libvips-linux-x64": "1.1.0", - "@img/sharp-libvips-linuxmusl-arm64": "1.1.0", - "@img/sharp-libvips-linuxmusl-x64": "1.1.0", - "@img/sharp-linux-arm": "0.34.2", - "@img/sharp-linux-arm64": "0.34.2", - "@img/sharp-linux-s390x": "0.34.2", - "@img/sharp-linux-x64": "0.34.2", - "@img/sharp-linuxmusl-arm64": "0.34.2", - "@img/sharp-linuxmusl-x64": "0.34.2", - "@img/sharp-wasm32": "0.34.2", - "@img/sharp-win32-arm64": "0.34.2", - "@img/sharp-win32-ia32": "0.34.2", - "@img/sharp-win32-x64": "0.34.2" } }, "node_modules/sharp/node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, "license": "ISC", - "optional": true, "bin": { "semver": "bin/semver.js" }, @@ -13113,12 +14399,59 @@ "dev": true, "license": "ISC" }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "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/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, + "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", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "is-arrayish": "^0.3.1" } @@ -13127,8 +14460,8 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "license": "MIT", - "optional": true + "devOptional": true, + "license": "MIT" }, "node_modules/sisteransi": { "version": "1.0.5", @@ -13307,6 +14640,30 @@ "node": ">=10.0.0" } }, + "node_modules/streamx": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", + "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/string_decoder": { + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -13645,6 +15002,33 @@ "url": "https://opencollective.com/synckit" } }, + "node_modules/tar-fs": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.0.tgz", + "integrity": "sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -13660,6 +15044,16 @@ "node": ">=8" } }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/throttleit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", @@ -14011,6 +15405,29 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.35.1.tgz", + "integrity": "sha512-xslJjFzhOmHYQzSB/QTeASAHbjmxOGEP6Coh93TXmUBFQoJ1VU35UHIDmG06Jd6taf3wqqC1ntBnCMeymy5Ovw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.35.1", + "@typescript-eslint/parser": "8.35.1", + "@typescript-eslint/utils": "8.35.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -14165,6 +15582,17 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/update-check": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz", + "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "registry-auth-token": "3.3.2", + "registry-url": "3.1.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -14195,6 +15623,13 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -14227,6 +15662,16 @@ "dev": true, "license": "MIT" }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -14417,6 +15862,69 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/widest-line/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==", + "dev": true, + "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/widest-line/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", diff --git a/package.json b/package.json index 958d3491..87974d43 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "scripts": { "dev": "next dev src/ui", "build": "next build src/ui", - "lint": "next lint src/ui", + "serve": "serve src/ui/out -c ../serve.json --cors", + "lint": "next lint --fix src/ui", "type-check": "tsc -p src/ui/tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit && tsc -p tsconfig.cypress.json --noEmit", "format": "prettier --write .", "prepare": "husky", @@ -49,14 +50,13 @@ "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", "@types/testing-library__jest-dom": "^5.14.9", - "@typescript-eslint/eslint-plugin": "^8.33.1", - "@typescript-eslint/parser": "^8.33.1", "cross-fetch": "^4.1.0", "cypress": "^13.13.3", "eslint": "^9.0.0", "eslint-config-next": "15.3.2", "eslint-config-prettier": "^8.5.0", "eslint-import-resolver-typescript": "^4.4.2", + "eslint-plugin-cypress": "^5.1.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-jest": "^28.11.0", "eslint-plugin-jsx-a11y": "^6.6.1", @@ -72,11 +72,10 @@ "jest-runner-groups": "^2.2.0", "jest-transform-stub": "^2.0.0", "prettier": "^3.5.3", - "typescript": "^5" - }, - "optionalDependencies": { - "@next/swc-linux-x64-gnu": "^15.3.3", - "@next/swc-linux-x64-musl": "^15.3.3" + "serve": "^14.2.4", + "sharp": "^0.32.0", + "typescript": "^5", + "typescript-eslint": "^8.34.0" }, "lint-staged": { "*.js": "eslint --cache --fix", diff --git a/pyproject.toml b/pyproject.toml index a78b1fc5..aef3f44a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,6 +53,7 @@ dependencies = [ "protobuf", "pydantic>=2.0.0", "pydantic-settings>=2.0.0", + "pyhumps>=3.8.0", "pyyaml>=6.0.0", "rich", "transformers", diff --git a/src/guidellm/__main__.py b/src/guidellm/__main__.py index 9e8a12fb..e8dee36d 100644 --- a/src/guidellm/__main__.py +++ b/src/guidellm/__main__.py @@ -206,7 +206,7 @@ def cli(): help=( "The path to save the output to. If it is a directory, " "it will save benchmarks.json under it. " - "Otherwise, json, yaml, or csv files are supported for output types " + "Otherwise, json, yaml, csv, or html files are supported for output types " "which will be read from the extension for the file path." ), ) diff --git a/src/guidellm/benchmark/output.py b/src/guidellm/benchmark/output.py index 4847160d..792097cb 100644 --- a/src/guidellm/benchmark/output.py +++ b/src/guidellm/benchmark/output.py @@ -6,6 +6,7 @@ from pathlib import Path from typing import Any, Literal, Optional, Union +import humps # type: ignore[import-not-found] import yaml from pydantic import Field from rich.console import Console @@ -25,6 +26,8 @@ StandardBaseModel, StatusDistributionSummary, ) +from guidellm.presentation import UIDataBuilder +from guidellm.presentation.injector import create_report from guidellm.scheduler import strategy_display_str from guidellm.utils import Colors, split_text_list_by_length @@ -68,6 +71,9 @@ def load_file(path: Union[str, Path]) -> "GenerativeBenchmarksReport": if type_ == "csv": raise ValueError(f"CSV file type is not supported for loading: {path}.") + if type_ == "html": + raise ValueError(f"HTML file type is not supported for loading: {path}.") + raise ValueError(f"Unsupported file type: {type_} for {path}.") benchmarks: list[GenerativeBenchmark] = Field( @@ -114,6 +120,9 @@ def save_file(self, path: Union[str, Path]) -> Path: if type_ == "csv": return self.save_csv(path) + if type_ == "html": + return self.save_html(path) + raise ValueError(f"Unsupported file type: {type_} for {path}.") def save_json(self, path: Union[str, Path]) -> Path: @@ -220,11 +229,29 @@ def save_csv(self, path: Union[str, Path]) -> Path: return path + def save_html(self, path: Union[str, Path]) -> Path: + """ + Download html, inject report data and save to a file. + + :param path: The path to create the report at. + :return: The path to the report. + """ + + data_builder = UIDataBuilder(self.benchmarks) + data = data_builder.to_dict() + camel_data = humps.camelize(data) + ui_api_data = {} + for k, v in camel_data.items(): + key = f"window.{humps.decamelize(k)} = {{}};" + value = f"window.{humps.decamelize(k)} = {json.dumps(v, indent=2)};\n" + ui_api_data[key] = value + return create_report(ui_api_data, path) + @staticmethod def _file_setup( path: Union[str, Path], - default_file_type: Literal["json", "yaml", "csv"] = "json", - ) -> tuple[Path, Literal["json", "yaml", "csv"]]: + default_file_type: Literal["json", "yaml", "csv", "html"] = "json", + ) -> tuple[Path, Literal["json", "yaml", "csv", "html"]]: path = Path(path) if not isinstance(path, Path) else path if path.is_dir(): @@ -242,6 +269,9 @@ def _file_setup( if path_suffix in [".csv"]: return path, "csv" + if path_suffix in [".html"]: + return path, "html" + raise ValueError(f"Unsupported file extension: {path_suffix} for {path}.") @staticmethod diff --git a/src/guidellm/config.py b/src/guidellm/config.py index ed7e782b..134d3208 100644 --- a/src/guidellm/config.py +++ b/src/guidellm/config.py @@ -30,10 +30,10 @@ class Environment(str, Enum): ENV_REPORT_MAPPING = { - Environment.PROD: "https://guidellm.neuralmagic.com/local-report/index.html", - Environment.STAGING: "https://staging.guidellm.neuralmagic.com/local-report/index.html", - Environment.DEV: "https://dev.guidellm.neuralmagic.com/local-report/index.html", - Environment.LOCAL: "tests/dummy/report.html", + Environment.PROD: "https://neuralmagic.github.io/guidellm/ui/latest/index.html", + Environment.STAGING: "https://neuralmagic.github.io/guidellm/ui/release/latest/index.html", + Environment.DEV: "https://neuralmagic.github.io/guidellm/ui/dev/index.html", + Environment.LOCAL: "http://localhost:3000/index.html", } @@ -87,6 +87,14 @@ class OpenAISettings(BaseModel): max_output_tokens: int = 16384 +class ReportGenerationSettings(BaseModel): + """ + Report generation settings for the application + """ + + source: str = "" + + class Settings(BaseSettings): """ All the settings are powered by pydantic_settings and could be @@ -140,6 +148,9 @@ class Settings(BaseSettings): ) openai: OpenAISettings = OpenAISettings() + # Report settings + report_generation: ReportGenerationSettings = ReportGenerationSettings() + # Output settings table_border_char: str = "=" table_headers_border_char: str = "-" @@ -148,6 +159,8 @@ class Settings(BaseSettings): @model_validator(mode="after") @classmethod def set_default_source(cls, values): + if not values.report_generation.source: + values.report_generation.source = ENV_REPORT_MAPPING.get(values.env) return values def generate_env_file(self) -> str: diff --git a/src/guidellm/objects/statistics.py b/src/guidellm/objects/statistics.py index 552b5c20..7831b2cf 100644 --- a/src/guidellm/objects/statistics.py +++ b/src/guidellm/objects/statistics.py @@ -37,6 +37,9 @@ class Percentiles(StandardBaseModel): p25: float = Field( description="The 25th percentile of the distribution.", ) + p50: float = Field( + description="The 50th percentile of the distribution.", + ) p75: float = Field( description="The 75th percentile of the distribution.", ) @@ -159,6 +162,7 @@ def from_distribution_function( p05=cdf[np.argmax(cdf[:, 1] >= 0.05), 0].item(), # noqa: PLR2004 p10=cdf[np.argmax(cdf[:, 1] >= 0.1), 0].item(), # noqa: PLR2004 p25=cdf[np.argmax(cdf[:, 1] >= 0.25), 0].item(), # noqa: PLR2004 + p50=cdf[np.argmax(cdf[:, 1] >= 0.50), 0].item(), # noqa: PLR2004 p75=cdf[np.argmax(cdf[:, 1] >= 0.75), 0].item(), # noqa: PLR2004 p90=cdf[np.argmax(cdf[:, 1] >= 0.9), 0].item(), # noqa: PLR2004 p95=cdf[np.argmax(cdf[:, 1] >= 0.95), 0].item(), # noqa: PLR2004 @@ -172,6 +176,7 @@ def from_distribution_function( p05=0, p10=0, p25=0, + p50=0, p75=0, p90=0, p95=0, diff --git a/src/guidellm/presentation/__init__.py b/src/guidellm/presentation/__init__.py new file mode 100644 index 00000000..872188db --- /dev/null +++ b/src/guidellm/presentation/__init__.py @@ -0,0 +1,28 @@ +from .builder import UIDataBuilder +from .data_models import ( + BenchmarkDatum, + Bucket, + Dataset, + Distribution, + Model, + RunInfo, + Server, + TokenDetails, + WorkloadDetails, +) +from .injector import create_report, inject_data + +__all__ = [ + "BenchmarkDatum", + "Bucket", + "Dataset", + "Distribution", + "Model", + "RunInfo", + "Server", + "TokenDetails", + "UIDataBuilder", + "WorkloadDetails", + "create_report", + "inject_data", +] diff --git a/src/guidellm/presentation/builder.py b/src/guidellm/presentation/builder.py new file mode 100644 index 00000000..a27d7cec --- /dev/null +++ b/src/guidellm/presentation/builder.py @@ -0,0 +1,27 @@ +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from guidellm.benchmark.benchmark import GenerativeBenchmark + +from .data_models import BenchmarkDatum, RunInfo, WorkloadDetails + + +class UIDataBuilder: + def __init__(self, benchmarks: list["GenerativeBenchmark"]): + self.benchmarks = benchmarks + + def build_run_info(self): + return RunInfo.from_benchmarks(self.benchmarks) + + def build_workload_details(self): + return WorkloadDetails.from_benchmarks(self.benchmarks) + + def build_benchmarks(self): + return [BenchmarkDatum.from_benchmark(b) for b in self.benchmarks] + + def to_dict(self) -> dict[str, Any]: + return { + "run_info": self.build_run_info().model_dump(), + "workload_details": self.build_workload_details().model_dump(), + "benchmarks": [b.model_dump() for b in self.build_benchmarks()], + } diff --git a/src/guidellm/presentation/data_models.py b/src/guidellm/presentation/data_models.py new file mode 100644 index 00000000..ff5221e3 --- /dev/null +++ b/src/guidellm/presentation/data_models.py @@ -0,0 +1,232 @@ +import random +from collections import defaultdict +from math import ceil +from typing import TYPE_CHECKING, Optional, Union + +from pydantic import BaseModel, computed_field + +if TYPE_CHECKING: + from guidellm.benchmark.benchmark import GenerativeBenchmark + +from guidellm.objects.statistics import DistributionSummary + + +class Bucket(BaseModel): + value: Union[float, int] + count: int + + @staticmethod + def from_data( + data: Union[list[float], list[int]], + bucket_width: Optional[float] = None, + n_buckets: Optional[int] = None, + ) -> tuple[list["Bucket"], float]: + if not data: + return [], 1.0 + + min_v = min(data) + max_v = max(data) + range_v = (1 + max_v) - min_v + + if bucket_width is None: + if n_buckets is None: + n_buckets = 10 + bucket_width = range_v / n_buckets + else: + n_buckets = ceil(range_v / bucket_width) + + bucket_counts: defaultdict[Union[float, int], int] = defaultdict(int) + for val in data: + idx = int((val - min_v) // bucket_width) + if idx >= n_buckets: + idx = n_buckets - 1 + bucket_start = min_v + idx * bucket_width + bucket_counts[bucket_start] += 1 + + buckets = [ + Bucket(value=start, count=count) + for start, count in sorted(bucket_counts.items()) + ] + return buckets, bucket_width + + +class Model(BaseModel): + name: str + size: int + + +class Dataset(BaseModel): + name: str + + +class RunInfo(BaseModel): + model: Model + task: str + timestamp: float + dataset: Dataset + + @classmethod + def from_benchmarks(cls, benchmarks: list["GenerativeBenchmark"]): + model = benchmarks[0].worker.backend_model or "N/A" + timestamp = max( + bm.run_stats.start_time for bm in benchmarks if bm.start_time is not None + ) + return cls( + model=Model(name=model, size=0), + task="N/A", + timestamp=timestamp, + dataset=Dataset(name="N/A"), + ) + + +class Distribution(BaseModel): + statistics: Optional[DistributionSummary] = None + buckets: list[Bucket] + bucket_width: float + + +class TokenDetails(BaseModel): + samples: list[str] + token_distributions: Distribution + + +class Server(BaseModel): + target: str + + +class RequestOverTime(BaseModel): + num_benchmarks: int + requests_over_time: Distribution + + +class WorkloadDetails(BaseModel): + prompts: TokenDetails + generations: TokenDetails + requests_over_time: RequestOverTime + rate_type: str + server: Server + + @classmethod + def from_benchmarks(cls, benchmarks: list["GenerativeBenchmark"]): + target = benchmarks[0].worker.backend_target + rate_type = benchmarks[0].args.profile.type_ + successful_requests = [ + req for bm in benchmarks for req in bm.requests.successful + ] + sample_indices = random.sample( + range(len(successful_requests)), min(5, len(successful_requests)) + ) + sample_prompts = [ + successful_requests[i].prompt.replace("\n", " ").replace('"', "'") + for i in sample_indices + ] + sample_outputs = [ + successful_requests[i].output.replace("\n", " ").replace('"', "'") + for i in sample_indices + ] + + prompt_tokens = [ + float(req.prompt_tokens) + for bm in benchmarks + for req in bm.requests.successful + ] + output_tokens = [ + float(req.output_tokens) + for bm in benchmarks + for req in bm.requests.successful + ] + + prompt_token_buckets, _prompt_token_bucket_width = Bucket.from_data( + prompt_tokens, 1 + ) + output_token_buckets, _output_token_bucket_width = Bucket.from_data( + output_tokens, 1 + ) + + prompt_token_stats = DistributionSummary.from_values(prompt_tokens) + output_token_stats = DistributionSummary.from_values(output_tokens) + prompt_token_distributions = Distribution( + statistics=prompt_token_stats, buckets=prompt_token_buckets, bucket_width=1 + ) + output_token_distributions = Distribution( + statistics=output_token_stats, buckets=output_token_buckets, bucket_width=1 + ) + + min_start_time = benchmarks[0].run_stats.start_time + + all_req_times = [ + req.start_time - min_start_time + for bm in benchmarks + for req in bm.requests.successful + if req.start_time is not None + ] + number_of_buckets = len(benchmarks) + request_over_time_buckets, bucket_width = Bucket.from_data( + all_req_times, None, number_of_buckets + ) + request_over_time_distribution = Distribution( + buckets=request_over_time_buckets, bucket_width=bucket_width + ) + return cls( + prompts=TokenDetails( + samples=sample_prompts, token_distributions=prompt_token_distributions + ), + generations=TokenDetails( + samples=sample_outputs, token_distributions=output_token_distributions + ), + requests_over_time=RequestOverTime( + requests_over_time=request_over_time_distribution, + num_benchmarks=number_of_buckets, + ), + rate_type=rate_type, + server=Server(target=target), + ) + + +class TabularDistributionSummary(DistributionSummary): + """ + Same fields as `DistributionSummary`, but adds a ready-to-serialize/iterate + `percentile_rows` helper. + """ + + @computed_field + def percentile_rows(self) -> list[dict[str, float]]: + rows = [ + {"percentile": name, "value": value} + for name, value in self.percentiles.model_dump().items() + ] + return list( + filter(lambda row: row["percentile"] in ["p50", "p90", "p95", "p99"], rows) + ) + + @classmethod + def from_distribution_summary( + cls, distribution: DistributionSummary + ) -> "TabularDistributionSummary": + return cls(**distribution.model_dump()) + + +class BenchmarkDatum(BaseModel): + requests_per_second: float + tpot: TabularDistributionSummary + ttft: TabularDistributionSummary + throughput: TabularDistributionSummary + time_per_request: TabularDistributionSummary + + @classmethod + def from_benchmark(cls, bm: "GenerativeBenchmark"): + return cls( + requests_per_second=bm.metrics.requests_per_second.successful.mean, + tpot=TabularDistributionSummary.from_distribution_summary( + bm.metrics.inter_token_latency_ms.successful + ), + ttft=TabularDistributionSummary.from_distribution_summary( + bm.metrics.time_to_first_token_ms.successful + ), + throughput=TabularDistributionSummary.from_distribution_summary( + bm.metrics.output_tokens_per_second.successful + ), + time_per_request=TabularDistributionSummary.from_distribution_summary( + bm.metrics.request_latency.successful + ), + ) diff --git a/src/guidellm/presentation/injector.py b/src/guidellm/presentation/injector.py new file mode 100644 index 00000000..a2a4855a --- /dev/null +++ b/src/guidellm/presentation/injector.py @@ -0,0 +1,62 @@ +import re +from pathlib import Path +from typing import Union + +from guidellm.config import settings +from guidellm.utils.text import load_text + + +def create_report(js_data: dict, output_path: Union[str, Path]) -> Path: + """ + Creates a report from the dictionary and saves it to the output path. + + :param js_data: dict with match str and json data to inject + :type js_data: dict + :param output_path: the file to save the report to. + :type output_path: str + :return: the path to the saved report + :rtype: str + """ + + if not isinstance(output_path, Path): + output_path = Path(output_path) + + html_content = load_text(settings.report_generation.source) + report_content = inject_data( + js_data, + html_content, + ) + + output_path.parent.mkdir(parents=True, exist_ok=True) + output_path.write_text(report_content) + return output_path + + +def inject_data( + js_data: dict, + html: str, +) -> str: + """ + Injects the json data into the HTML, + replacing placeholders only within the
section. + + :param js_data: the json data to inject + :type js_data: dict + :param html: the html to inject the data into + :type html: str + :return: the html with the json data injected + :rtype: str + """ + head_match = re.search(r"]*>(.*?)", html, re.DOTALL | re.IGNORECASE) + if not head_match: + return html # or raise error? + + head_content = head_match.group(1) + + # Replace placeholders only inside the content + for placeholder, script in js_data.items(): + head_content = head_content.replace(placeholder, script) + + # Rebuild the HTML + new_head = f"{head_content}" + return html[: head_match.start()] + new_head + html[head_match.end() :] diff --git a/src/ui/.env.development b/src/ui/.env.development index 66c8d235..2d03b789 100644 --- a/src/ui/.env.development +++ b/src/ui/.env.development @@ -1,3 +1,3 @@ -ASSET_PREFIX=https://review.neuralmagic.com/guidellm-ui/dev/_next -BASE_PATH=/guidellm-ui/dev +ASSET_PREFIX=https://neuralmagic.github.io/guidellm/ui/dev +BASE_PATH=/ui/dev NEXT_PUBLIC_USE_MOCK_API=true diff --git a/src/ui/.env.example b/src/ui/.env.example index 06812a30..b9d5ff2b 100644 --- a/src/ui/.env.example +++ b/src/ui/.env.example @@ -1,3 +1,4 @@ ASSET_PREFIX=http://localhost:3000 BASE_PATH=http://localhost:3000 NEXT_PUBLIC_USE_MOCK_API=true +USE_MOCK_DATA=false diff --git a/src/ui/.env.local b/src/ui/.env.local index 44ab168b..b9d5ff2b 100644 --- a/src/ui/.env.local +++ b/src/ui/.env.local @@ -1,4 +1,4 @@ ASSET_PREFIX=http://localhost:3000 BASE_PATH=http://localhost:3000 NEXT_PUBLIC_USE_MOCK_API=true -USE_MOCK_DATA=true +USE_MOCK_DATA=false diff --git a/src/ui/.env.production b/src/ui/.env.production index 66c8d235..981e7d86 100644 --- a/src/ui/.env.production +++ b/src/ui/.env.production @@ -1,3 +1,3 @@ -ASSET_PREFIX=https://review.neuralmagic.com/guidellm-ui/dev/_next -BASE_PATH=/guidellm-ui/dev +ASSET_PREFIX=https://neuralmagic.github.io/guidellm/ui/latest +BASE_PATH=/ui/latest NEXT_PUBLIC_USE_MOCK_API=true diff --git a/src/ui/.env.staging b/src/ui/.env.staging index 20142e5d..416e04c3 100644 --- a/src/ui/.env.staging +++ b/src/ui/.env.staging @@ -1,3 +1,3 @@ -ASSET_PREFIX=https://staging.guidellm.neuralmagic.com -BASE_PATH=/guidellm-ui/staging +ASSET_PREFIX=https://neuralmagic.github.io/guidellm/ui/release/latest +BASE_PATH=/ui/release/latest NEXT_PUBLIC_USE_MOCK_API=true diff --git a/src/ui/app/theme.ts b/src/ui/app/theme.ts index fac120eb..328388e4 100644 --- a/src/ui/app/theme.ts +++ b/src/ui/app/theme.ts @@ -50,7 +50,6 @@ import { RED_SHADES, OUTER_SPACE_GRAY, } from '../lib/utils/Colors'; - // Spezia import SpeziaMedium from './assets/fonts/spezia/Spezia-Medium.otf'; import SpeziaRegular from './assets/fonts/spezia/Spezia-Regular.otf'; diff --git a/src/ui/lib/components/BlockHeader/BlockHeader.component.tsx b/src/ui/lib/components/BlockHeader/BlockHeader.component.tsx index 8e8587ee..0b53fe46 100644 --- a/src/ui/lib/components/BlockHeader/BlockHeader.component.tsx +++ b/src/ui/lib/components/BlockHeader/BlockHeader.component.tsx @@ -1,9 +1,8 @@ 'use client'; import { Box, Typography, useTheme } from '@mui/material'; -import { Info } from '@assets/icons'; - import { SvgContainer } from '@/lib/utils/SvgContainer'; +import { Info } from '@assets/icons'; import { BlockHeaderProps } from './BlockHeader.interfaces'; import { CustomDivider } from './BlockHeader.styles'; diff --git a/src/ui/lib/components/Charts/Combined/components/CustomLegendLayer/index.tsx b/src/ui/lib/components/Charts/Combined/components/CustomLegendLayer/index.tsx index 3f390695..40214bbd 100644 --- a/src/ui/lib/components/Charts/Combined/components/CustomLegendLayer/index.tsx +++ b/src/ui/lib/components/Charts/Combined/components/CustomLegendLayer/index.tsx @@ -1,8 +1,7 @@ import { useTheme } from '@mui/material'; -import useLineColors from '../../../common/useLineColors'; - import { CustomLegendLayerProps } from './CustomLegendLayer.interfaces'; +import useLineColors from '../../../common/useLineColors'; const LEGEND_HEIGHT = 20; diff --git a/src/ui/lib/components/Charts/Combined/components/DottedLines/index.tsx b/src/ui/lib/components/Charts/Combined/components/DottedLines/index.tsx index b4df2145..c8a76069 100644 --- a/src/ui/lib/components/Charts/Combined/components/DottedLines/index.tsx +++ b/src/ui/lib/components/Charts/Combined/components/DottedLines/index.tsx @@ -1,8 +1,7 @@ import React from 'react'; -import useLineColors from '../../../common/useLineColors'; - import { DottedLinesProps } from './DottedLines.interfaces'; +import useLineColors from '../../../common/useLineColors'; const DottedLines = ({ lines, diff --git a/src/ui/lib/components/Charts/DashedLine/DashedLine.component.tsx b/src/ui/lib/components/Charts/DashedLine/DashedLine.component.tsx index a3b9ab87..650c62ee 100644 --- a/src/ui/lib/components/Charts/DashedLine/DashedLine.component.tsx +++ b/src/ui/lib/components/Charts/DashedLine/DashedLine.component.tsx @@ -7,7 +7,7 @@ import { DashedLineProps, ScaleType } from './DashedLine.interfaces'; import { spacedLogValues } from './helpers'; export const getMinTick = (data: readonly Serie[]) => { - return Math.max( + return Math.min( ...data.map((lineData) => Math.min(...lineData.data.map((point) => point.y as number)) ) @@ -80,11 +80,16 @@ export const Component = ({ let extraLeftAxisOptions = {}; let extraYScaleOptions = {}; if (yScaleType === ScaleType.log) { - const ticks = spacedLogValues(getMinTick(data), getMaxTick(data), 6); + const ticks = spacedLogValues( + Math.floor(getMinTick(data)), + Math.ceil(getMaxTick(data)), + 6 + ); extraLeftAxisOptions = { tickValues: ticks, }; extraYScaleOptions = { + min: ticks[0], max: ticks[ticks.length - 1], }; } diff --git a/src/ui/lib/components/Charts/DashedLine/components/CustomLegendLayer/index.tsx b/src/ui/lib/components/Charts/DashedLine/components/CustomLegendLayer/index.tsx index 81bd8c20..bc04c481 100644 --- a/src/ui/lib/components/Charts/DashedLine/components/CustomLegendLayer/index.tsx +++ b/src/ui/lib/components/Charts/DashedLine/components/CustomLegendLayer/index.tsx @@ -39,7 +39,7 @@ export const CustomLegendLayer = ({ series, ...rest }: CustomLegendLayerProps) = y1="0" x2="20" y2="0" - stroke={getColor(item.solid)} + stroke={getColor(Boolean(item.solid))} strokeWidth={2} strokeDasharray={item?.solid ? '' : '4,4'} /> diff --git a/src/ui/lib/components/Charts/DashedLine/components/DashedSolidLine/index.tsx b/src/ui/lib/components/Charts/DashedLine/components/DashedSolidLine/index.tsx index dd786b6a..952ff24a 100644 --- a/src/ui/lib/components/Charts/DashedLine/components/DashedSolidLine/index.tsx +++ b/src/ui/lib/components/Charts/DashedLine/components/DashedSolidLine/index.tsx @@ -1,8 +1,7 @@ import { useTheme } from '@mui/material'; -import { toNumberValue } from '../../helpers'; - import { DashedSolidLineProps } from './DashedSolidLine.interfaces'; +import { toNumberValue } from '../../helpers'; export const DashedSolidLine = ({ series, @@ -48,7 +47,7 @@ export const DashedSolidLine = ({ return linePath !== null ? linePath : undefined; })()} fill="none" - stroke={getColor(solid)} + stroke={getColor(Boolean(solid))} style={ !solid ? { @@ -67,7 +66,7 @@ export const DashedSolidLine = ({ cx={xScale(toNumberValue(d.data.x))} cy={yScale(toNumberValue(d.data.y))} r={4} - fill={getColor(solid)} + fill={getColor(Boolean(solid))} /> ))} diff --git a/src/ui/lib/components/Charts/DashedLine/helpers.ts b/src/ui/lib/components/Charts/DashedLine/helpers.ts index c73405ed..0c5a6bd0 100644 --- a/src/ui/lib/components/Charts/DashedLine/helpers.ts +++ b/src/ui/lib/components/Charts/DashedLine/helpers.ts @@ -13,6 +13,21 @@ const allowedMultipliers = [ 1, 1.2, 1.4, 1.5, 1.6, 1.8, 2, 2.5, 3, 3.5, 4, 5, 6, 7, 7.5, 8, 9, 10, ]; +export function roundDownNice(x: number) { + if (x <= 0) { + return x; + } + const exponent = Math.floor(Math.log10(x)); + const base = Math.pow(10, exponent); + const fraction = x / base; + for (const m of allowedMultipliers.slice().reverse()) { + if (m <= fraction) { + return Math.floor(m * base); + } + } + return Math.floor(10 * base); +} + export function roundUpNice(x: number) { if (x <= 0) { return x; @@ -22,10 +37,10 @@ export function roundUpNice(x: number) { const fraction = x / base; for (const m of allowedMultipliers) { if (m >= fraction) { - return Math.round(m * base); + return Math.ceil(m * base); } } - return Math.round(10 * base); + return Math.ceil(10 * base); } export function roundNearestNice(x: number) { @@ -51,11 +66,14 @@ export function spacedLogValues(min: number, max: number, steps: number) { if (steps < 2) { return []; } + if (steps > max - min) { + steps = max - min + 1; + } if (min === 0) { const nonzeroCount = steps - 1; - const exponent = Math.floor(Math.log10(max)) - (nonzeroCount - 1); - const lowerNonZero = roundNearestNice(Math.pow(10, exponent)); + const exponent = Math.log10(max) / (nonzeroCount - 1); + const lowerNonZero = roundDownNice(Math.pow(10, exponent)); const upperTick = roundUpNice(max); const r = Math.pow(upperTick / lowerNonZero, 1 / (nonzeroCount - 1)); const ticks = [0]; @@ -65,7 +83,7 @@ export function spacedLogValues(min: number, max: number, steps: number) { } return ticks; } else { - const lowerTick = roundUpNice(min); + const lowerTick = roundNearestNice(min); const upperTick = roundUpNice(max); const r = Math.pow(upperTick / lowerTick, 1 / (steps - 1)); const ticks = []; diff --git a/src/ui/lib/components/Charts/MetricLine/MetricLine.component.tsx b/src/ui/lib/components/Charts/MetricLine/MetricLine.component.tsx index f99c98f1..8b1b4df2 100644 --- a/src/ui/lib/components/Charts/MetricLine/MetricLine.component.tsx +++ b/src/ui/lib/components/Charts/MetricLine/MetricLine.component.tsx @@ -2,9 +2,9 @@ import { useTheme } from '@mui/material'; import { ResponsiveLine } from '@nivo/line'; import React, { FC } from 'react'; -import { MetricLineProps } from '.'; import { useColor } from '@/lib/hooks/useColor'; +import { MetricLineProps } from '.'; import CustomAxes from './components/CustomAxes'; import ThresholdBar from './components/ThresholdBar'; import { ScaleType } from '../DashedLine/DashedLine.interfaces'; @@ -59,8 +59,8 @@ export const Component: FC