From 7752a6287e1d1c180ee565603312decd74bf3efb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= Date: Sun, 6 Sep 2026 02:05:30 +0200 Subject: [PATCH 01/41] Modernize BookIT stack and preserve booking workflows Migrate to pnpm 12 and Vite+, React 19 with shadcn and FullCalendar, TanStack Router and Query, Apollo Server, Prisma 7, and generated TypeScript contracts. Restore compact room filters and original calendar, booking, and rules behavior. Add signed sessions, private phone fields, serializable booking writes, modern CI, and real Gamma Playwright coverage. Keep PostgreSQL 12 and Redis 5 server upgrades as documented separate follow-ups. Verified 45 unit/API tests, four E2E flows, strict types and lint, production builds, clean dependency audit, and local Docker smoke tests. --- .dockerignore | 16 + .github/dependabot.yml | 21 + .github/workflows/backend.yml | 50 - .github/workflows/ci.yml | 122 + .github/workflows/codeql.yml | 30 + .github/workflows/frontend.yml | 50 - .gitignore | 8 + .node-version | 1 + .travis.yml | 9 - README.md | 190 +- backend/.env.example | 19 +- backend/.eslintrc.yml | 13 - backend/.prettierrc | 12 - backend/Dockerfile | 46 +- backend/dev_setup.sh | 3 +- backend/package-lock.json | 9418 ------- backend/package.json | 81 +- backend/prisma.config.ts | 9 + backend/prisma/schema.prisma | 1 - backend/src/auth/session-store.ts | 47 + backend/src/auth/user.ts | 43 + backend/src/generated/schema.ts | 369 + backend/src/index.ts | 154 +- backend/src/models/event.ts | 15 +- backend/src/models/rule.ts | 34 +- backend/src/models/user.ts | 2 +- backend/src/resolvers/event.resolver.ts | 50 +- .../src/resolvers/illegal_slots.resolver.ts | 24 +- backend/src/resolvers/index.ts | 7 +- backend/src/resolvers/rule.resolver.ts | 27 +- backend/src/resolvers/serialize.ts | 28 + backend/src/resolvers/user.resolver.ts | 2 +- backend/src/routes/index.ts | 62 +- backend/src/schemas/v1/event.gql | 16 +- backend/src/schemas/v1/illegal_slots.gql | 1 + backend/src/schemas/v1/rule.gql | 16 +- backend/src/services/event.service.ts | 215 +- backend/src/services/rule.service.ts | 148 +- backend/src/tests/booking-transaction.test.ts | 156 + backend/src/tests/graphql.test.ts | 137 + backend/src/tests/rule.test.ts | 161 +- backend/src/tests/session-store.test.ts | 66 + backend/src/utils/commonTypes.ts | 5 + backend/src/utils/index.ts | 34 +- backend/startup.sh | 13 +- backend/tsconfig.build.json | 4 + backend/tsconfig.json | 10 +- codegen.ts | 52 + db-scripts/.jobber | 6 - db-scripts/Dockerfile | 17 +- db-scripts/cleanup.sh | 14 + docker-compose.yml | 9 +- e2e/README.md | 39 + e2e/admin-rules.spec.ts | 58 + e2e/booking-helpers.ts | 70 + e2e/booking-lifecycle.spec.ts | 92 + e2e/calendar.spec.ts | 46 + e2e/compose.ts | 400 + e2e/dev.ts | 43 + e2e/fixtures.ts | 48 + e2e/permissions.spec.ts | 85 + e2e/tsconfig.json | 14 + frontend/.prettierrc | 12 - frontend/Dockerfile | 25 +- frontend/components.json | 21 + frontend/default.conf | 9 +- frontend/dev_setup.sh | 5 +- frontend/index.html | 13 + frontend/package-lock.json | 21999 ---------------- frontend/package.json | 76 +- frontend/public/index.html | 22 - frontend/public/manifest.json | 3 +- frontend/src/App.jsx | 27 - frontend/src/api/backend.api.js | 178 - frontend/src/api/backend.queries.js | 150 - frontend/src/api/client.test.ts | 32 + frontend/src/api/client.ts | 29 + frontend/src/api/operations.graphql | 88 + .../event-form/elements/book-as.element.jsx | 17 - .../elements/booking-terms.element.jsx | 29 - .../event-form/elements/cubsec.element.jsx | 29 - .../elements/date-time-picker.element.jsx | 23 - .../elements/description.element.jsx | 19 - .../event-form/elements/gdpr.element.jsx | 25 - .../components/event-form/elements/index.jsx | 19 - .../elements/phone-number.element.jsx | 17 - .../event-form/elements/rooms.element.jsx | 17 - .../event-form/elements/title.element.jsx | 17 - .../components/event-form/event.form.css | 4 - .../components/event-form/event.form.jsx | 160 - .../event-form/event.form.translations.json | 52 - .../common/components/event-form/index.jsx | 3 - .../src/common/components/header/index.jsx | 25 - .../components/header/tabs.component.jsx | 45 - .../components/header/tabs.translations.json | 4 - .../common/contexts/user-context/index.jsx | 35 - .../add-event-button/add-event-button.css | 9 - .../elements/add-event-button/index.jsx | 19 - .../src/common/hooks/change-calendar-size.js | 28 - frontend/src/common/hooks/use-mobile-query.js | 20 - frontend/src/common/rooms.js | 24 - frontend/src/components/booking-form.tsx | 303 + .../src/components/event-calendar-icons.tsx | 28 + .../src/components/event-calendar-toolbar.tsx | 82 + frontend/src/components/event-calendar.tsx | 89 + frontend/src/components/feedback.tsx | 30 + frontend/src/components/ui/alert.tsx | 59 + frontend/src/components/ui/badge.tsx | 45 + frontend/src/components/ui/button.tsx | 61 + frontend/src/components/ui/card.tsx | 74 + frontend/src/components/ui/checkbox.tsx | 26 + frontend/src/components/ui/dialog.tsx | 144 + .../components/ui/event-calendar-views.tsx | 464 + frontend/src/components/ui/input.tsx | 20 + frontend/src/components/ui/label.tsx | 18 + frontend/src/components/ui/select.tsx | 174 + frontend/src/components/ui/separator.tsx | 27 + frontend/src/components/ui/skeleton.tsx | 13 + frontend/src/components/ui/table.tsx | 89 + frontend/src/components/ui/tabs.tsx | 78 + frontend/src/components/ui/textarea.tsx | 17 + frontend/src/generated/graphql.ts | 113 + frontend/src/index.js | 16 - frontend/src/lib/dates.test.ts | 18 + frontend/src/lib/dates.ts | 9 + frontend/src/lib/forms.ts | 4 + frontend/src/lib/language.tsx | 23 + .../gdpr-agreement.js => lib/privacy.ts} | 7 +- frontend/src/lib/rooms.ts | 18 + frontend/src/lib/user.ts | 16 + frontend/src/lib/utils.ts | 5 + frontend/src/main.tsx | 21 + frontend/src/pages/booking.tsx | 198 + frontend/src/pages/calendar.tsx | 226 + frontend/src/pages/rules.tsx | 480 + frontend/src/reportWebVitals.js | 13 - frontend/src/router.tsx | 165 + frontend/src/styles.css | 102 + frontend/src/use-cases/auth/index.jsx | 24 - .../edit-event/edit-event.translations.json | 4 - frontend/src/use-cases/edit-event/index.jsx | 83 - .../src/use-cases/home/home.translations.json | 4 - frontend/src/use-cases/home/index.css | 8 - frontend/src/use-cases/home/index.jsx | 194 - .../use-cases/home/views/calendar-view.css | 14 - .../use-cases/home/views/calendar.view.jsx | 68 - .../use-cases/home/views/detailed-view.css | 4 - .../views/detailed-view.translations.json | 8 - .../home/views/detailed-view.view.jsx | 103 - frontend/src/use-cases/new-event/index.jsx | 75 - .../new-event/new-event.translations.json | 4 - .../src/use-cases/rules/day-mask.element.jsx | 61 - .../src/use-cases/rules/day-mask.style.css | 30 - frontend/src/use-cases/rules/index.css | 6 - frontend/src/use-cases/rules/index.jsx | 115 - .../src/use-cases/rules/rooms.element.jsx | 14 - frontend/src/use-cases/rules/rule.form.jsx | 83 - frontend/src/use-cases/rules/rules.labels.js | 25 - .../use-cases/rules/rules.translations.json | 74 - frontend/src/utils/utils.js | 19 - frontend/src/vite-env.d.ts | 1 + frontend/tsconfig.json | 19 + frontend/vite.config.ts | 14 + package.json | 37 + playwright.config.ts | 14 + pnpm-lock.yaml | 9728 +++++++ pnpm-workspace.yaml | 35 + tsconfig.json | 4 + vite.config.ts | 32 + 169 files changed, 16147 insertions(+), 34299 deletions(-) create mode 100644 .dockerignore create mode 100644 .github/dependabot.yml delete mode 100644 .github/workflows/backend.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/codeql.yml delete mode 100644 .github/workflows/frontend.yml create mode 100644 .node-version delete mode 100644 .travis.yml delete mode 100644 backend/.eslintrc.yml delete mode 100644 backend/.prettierrc delete mode 100644 backend/package-lock.json create mode 100644 backend/prisma.config.ts create mode 100644 backend/src/auth/session-store.ts create mode 100644 backend/src/auth/user.ts create mode 100644 backend/src/generated/schema.ts create mode 100644 backend/src/resolvers/serialize.ts create mode 100644 backend/src/tests/booking-transaction.test.ts create mode 100644 backend/src/tests/graphql.test.ts create mode 100644 backend/src/tests/session-store.test.ts create mode 100644 backend/tsconfig.build.json create mode 100644 codegen.ts delete mode 100644 db-scripts/.jobber create mode 100644 db-scripts/cleanup.sh create mode 100644 e2e/README.md create mode 100644 e2e/admin-rules.spec.ts create mode 100644 e2e/booking-helpers.ts create mode 100644 e2e/booking-lifecycle.spec.ts create mode 100644 e2e/calendar.spec.ts create mode 100644 e2e/compose.ts create mode 100644 e2e/dev.ts create mode 100644 e2e/fixtures.ts create mode 100644 e2e/permissions.spec.ts create mode 100644 e2e/tsconfig.json delete mode 100644 frontend/.prettierrc create mode 100644 frontend/components.json create mode 100644 frontend/index.html delete mode 100644 frontend/package-lock.json delete mode 100644 frontend/public/index.html delete mode 100644 frontend/src/App.jsx delete mode 100644 frontend/src/api/backend.api.js delete mode 100644 frontend/src/api/backend.queries.js create mode 100644 frontend/src/api/client.test.ts create mode 100644 frontend/src/api/client.ts create mode 100644 frontend/src/api/operations.graphql delete mode 100644 frontend/src/common/components/event-form/elements/book-as.element.jsx delete mode 100644 frontend/src/common/components/event-form/elements/booking-terms.element.jsx delete mode 100644 frontend/src/common/components/event-form/elements/cubsec.element.jsx delete mode 100644 frontend/src/common/components/event-form/elements/date-time-picker.element.jsx delete mode 100644 frontend/src/common/components/event-form/elements/description.element.jsx delete mode 100644 frontend/src/common/components/event-form/elements/gdpr.element.jsx delete mode 100644 frontend/src/common/components/event-form/elements/index.jsx delete mode 100644 frontend/src/common/components/event-form/elements/phone-number.element.jsx delete mode 100644 frontend/src/common/components/event-form/elements/rooms.element.jsx delete mode 100644 frontend/src/common/components/event-form/elements/title.element.jsx delete mode 100644 frontend/src/common/components/event-form/event.form.css delete mode 100644 frontend/src/common/components/event-form/event.form.jsx delete mode 100644 frontend/src/common/components/event-form/event.form.translations.json delete mode 100644 frontend/src/common/components/event-form/index.jsx delete mode 100644 frontend/src/common/components/header/index.jsx delete mode 100644 frontend/src/common/components/header/tabs.component.jsx delete mode 100644 frontend/src/common/components/header/tabs.translations.json delete mode 100644 frontend/src/common/contexts/user-context/index.jsx delete mode 100644 frontend/src/common/elements/add-event-button/add-event-button.css delete mode 100644 frontend/src/common/elements/add-event-button/index.jsx delete mode 100644 frontend/src/common/hooks/change-calendar-size.js delete mode 100644 frontend/src/common/hooks/use-mobile-query.js delete mode 100644 frontend/src/common/rooms.js create mode 100644 frontend/src/components/booking-form.tsx create mode 100644 frontend/src/components/event-calendar-icons.tsx create mode 100644 frontend/src/components/event-calendar-toolbar.tsx create mode 100644 frontend/src/components/event-calendar.tsx create mode 100644 frontend/src/components/feedback.tsx create mode 100644 frontend/src/components/ui/alert.tsx create mode 100644 frontend/src/components/ui/badge.tsx create mode 100644 frontend/src/components/ui/button.tsx create mode 100644 frontend/src/components/ui/card.tsx create mode 100644 frontend/src/components/ui/checkbox.tsx create mode 100644 frontend/src/components/ui/dialog.tsx create mode 100644 frontend/src/components/ui/event-calendar-views.tsx create mode 100644 frontend/src/components/ui/input.tsx create mode 100644 frontend/src/components/ui/label.tsx create mode 100644 frontend/src/components/ui/select.tsx create mode 100644 frontend/src/components/ui/separator.tsx create mode 100644 frontend/src/components/ui/skeleton.tsx create mode 100644 frontend/src/components/ui/table.tsx create mode 100644 frontend/src/components/ui/tabs.tsx create mode 100644 frontend/src/components/ui/textarea.tsx create mode 100644 frontend/src/generated/graphql.ts delete mode 100644 frontend/src/index.js create mode 100644 frontend/src/lib/dates.test.ts create mode 100644 frontend/src/lib/dates.ts create mode 100644 frontend/src/lib/forms.ts create mode 100644 frontend/src/lib/language.tsx rename frontend/src/{common/components/event-form/gdpr-agreement.js => lib/privacy.ts} (95%) create mode 100644 frontend/src/lib/rooms.ts create mode 100644 frontend/src/lib/user.ts create mode 100644 frontend/src/lib/utils.ts create mode 100644 frontend/src/main.tsx create mode 100644 frontend/src/pages/booking.tsx create mode 100644 frontend/src/pages/calendar.tsx create mode 100644 frontend/src/pages/rules.tsx delete mode 100644 frontend/src/reportWebVitals.js create mode 100644 frontend/src/router.tsx create mode 100644 frontend/src/styles.css delete mode 100644 frontend/src/use-cases/auth/index.jsx delete mode 100644 frontend/src/use-cases/edit-event/edit-event.translations.json delete mode 100644 frontend/src/use-cases/edit-event/index.jsx delete mode 100644 frontend/src/use-cases/home/home.translations.json delete mode 100644 frontend/src/use-cases/home/index.css delete mode 100644 frontend/src/use-cases/home/index.jsx delete mode 100644 frontend/src/use-cases/home/views/calendar-view.css delete mode 100644 frontend/src/use-cases/home/views/calendar.view.jsx delete mode 100644 frontend/src/use-cases/home/views/detailed-view.css delete mode 100644 frontend/src/use-cases/home/views/detailed-view.translations.json delete mode 100644 frontend/src/use-cases/home/views/detailed-view.view.jsx delete mode 100644 frontend/src/use-cases/new-event/index.jsx delete mode 100644 frontend/src/use-cases/new-event/new-event.translations.json delete mode 100644 frontend/src/use-cases/rules/day-mask.element.jsx delete mode 100644 frontend/src/use-cases/rules/day-mask.style.css delete mode 100644 frontend/src/use-cases/rules/index.css delete mode 100644 frontend/src/use-cases/rules/index.jsx delete mode 100644 frontend/src/use-cases/rules/rooms.element.jsx delete mode 100644 frontend/src/use-cases/rules/rule.form.jsx delete mode 100644 frontend/src/use-cases/rules/rules.labels.js delete mode 100644 frontend/src/use-cases/rules/rules.translations.json delete mode 100644 frontend/src/utils/utils.js create mode 100644 frontend/src/vite-env.d.ts create mode 100644 frontend/tsconfig.json create mode 100644 frontend/vite.config.ts create mode 100644 package.json create mode 100644 playwright.config.ts create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b618b4f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,16 @@ +.git +**/node_modules +**/.env +**/.env.* +!**/.env.example +**/build +**/dist +**/.vite +**/.cache +**/coverage +playwright-report +test-results +e2e +.codex +.agents +*.log diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..1c35bad --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,21 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + groups: + actions: + patterns: ["*"] + - package-ecosystem: npm + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 5 + groups: + prisma: + patterns: ["prisma", "@prisma/*"] + graphql-codegen: + patterns: ["@graphql-codegen/*"] + react: + patterns: ["react", "react-dom", "@types/react", "@types/react-dom"] diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml deleted file mode 100644 index 92ed9f3..0000000 --- a/.github/workflows/backend.yml +++ /dev/null @@ -1,50 +0,0 @@ -on: - push: - branches: [main] - pull_request: - branches: [main] - -name: "Backend CI" - -jobs: - build: - name: Build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 - with: - node-version: 16 - - name: Install dependencies - run: npm install - working-directory: backend - - name: Build - run: npm run build - working-directory: backend - - docker: - runs-on: ubuntu-latest - needs: - - build - permissions: - packages: write - steps: - - uses: actions/checkout@v3 - - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v2 - - name: Login to GitHub Container Registry - if: ${{ github.ref == 'refs/heads/main' }} - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push - id: docker_build - uses: docker/build-push-action@v2 - with: - context: ./backend - file: ./backend/Dockerfile - push: ${{ github.ref == 'refs/heads/main' }} - tags: ghcr.io/cthit/bookit-node-backend:latest diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3a9d8d7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,122 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + CI: true + +jobs: + quality: + name: Types, lint, tests, build, and dependency audit + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: voidzero-dev/setup-vp@49c3e4e92c52e7f8392712a9267bbe71c5ab30e5 # v1.19.0 + with: + node-version: "24.19.0" + version: "0.3.0" + run-install: false + cache: true + - name: Install approved dependencies from the lockfile + run: vp install --frozen-lockfile + - name: Generate GraphQL contracts and check they are committed + run: | + pnpm codegen + git diff --exit-code -- backend/src/generated/schema.ts frontend/src/generated/graphql.ts + - name: Generate Prisma client + run: pnpm --dir backend exec prisma generate + - name: Check strict TypeScript contracts + run: pnpm typecheck + - name: Check formatting and type-aware lint + run: vp check + - name: Run unit and API contract tests + run: pnpm test + - name: Build backend and frontend + run: pnpm build + - name: Audit production and development dependencies + run: pnpm audit --audit-level low + + e2e: + name: Browser flows with real Gamma, PostgreSQL, and Redis + needs: quality + runs-on: ubuntu-24.04 + timeout-minutes: 30 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: voidzero-dev/setup-vp@49c3e4e92c52e7f8392712a9267bbe71c5ab30e5 # v1.19.0 + with: + node-version: "24.19.0" + version: "0.3.0" + run-install: false + cache: true + - run: vp install --frozen-lockfile + - run: pnpm codegen + - name: Install Chromium and its system dependencies + run: pnpm exec playwright install --with-deps chromium + - name: Verify Docker is available for Testcontainers + run: docker info + - name: Run authenticated browser flows + run: pnpm test:e2e + - name: Preserve failing browser traces, screenshots, videos, and logs + if: failure() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: playwright-failure-artifacts + path: | + playwright-report/ + test-results/ + if-no-files-found: ignore + retention-days: 7 + + containers: + name: Build ${{ matrix.component }} container + needs: [quality, e2e] + runs-on: ubuntu-24.04 + timeout-minutes: 30 + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + component: [backend, frontend] + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 + - name: Log in to GitHub Container Registry + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build image and publish successful main builds + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 + with: + context: . + file: ${{ matrix.component }}/Dockerfile + push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + tags: | + ghcr.io/cthit/bookit-node-${{ matrix.component }}:latest + ghcr.io/cthit/bookit-node-${{ matrix.component }}:${{ github.sha }} + cache-from: type=gha,scope=${{ matrix.component }} + cache-to: type=gha,mode=max,scope=${{ matrix.component }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..e39bee6 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,30 @@ +name: CodeQL +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + - cron: "17 5 * * 2" + workflow_dispatch: +permissions: + contents: read +jobs: + analyze: + runs-on: ubuntu-24.04 + timeout-minutes: 20 + permissions: + contents: read + security-events: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 + with: + languages: javascript-typescript + build-mode: none + queries: security-extended + - uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 + with: + category: /language:javascript-typescript diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml deleted file mode 100644 index 2b5dcdd..0000000 --- a/.github/workflows/frontend.yml +++ /dev/null @@ -1,50 +0,0 @@ -on: - push: - branches: [main] - pull_request: - branches: [main] - -name: "Frontend CI" - -jobs: - build: - name: Build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 - with: - node-version: 16 - - name: Install dependencies - run: npm install - working-directory: frontend - - name: Build - run: npm run build - working-directory: frontend - - docker: - runs-on: ubuntu-latest - needs: - - build - permissions: - packages: write - steps: - - uses: actions/checkout@v3 - - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v2 - - name: Login to GitHub Container Registry - if: ${{ github.ref == 'refs/heads/main' }} - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push - id: docker_build - uses: docker/build-push-action@v2 - with: - context: ./frontend - file: ./frontend/Dockerfile - push: ${{ github.ref == 'refs/heads/main' }} - tags: ghcr.io/cthit/bookit-node-frontend:latest diff --git a/.gitignore b/.gitignore index 425a63a..9f2e3fd 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,11 @@ build .vscode/ package-lock.json .env +.env.* +!.env.example +.pnpm-store/ +dist/ +.vite/ +playwright-report/ +test-results/ +coverage/ diff --git a/.node-version b/.node-version new file mode 100644 index 0000000..60ade1a --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +24.19.0 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 533286b..0000000 --- a/.travis.yml +++ /dev/null @@ -1,9 +0,0 @@ -language: node_js -node_js: - - "14" -before_install: - - cd backend -install: - - npm i -script: - - npm test diff --git a/README.md b/README.md index 76fa8f1..74dac51 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,178 @@ # bookIT -[![Build Status](https://travis-ci.com/molleer/bookit.svg?token=ES9SJGmRYiEL9bzd8RLb&branch=main)](https://travis-ci.com/molleer/bookit) +Booking service for Teknologsektionen Informationsteknik at Chalmers. -A booking service for the Chalmers Software Engineering Student Division (IT) +## Stack -## Contributors +- Node 24.19 LTS and pnpm 12.3.4, pinned in `.node-version` and `packageManager`. +- React 19, TypeScript, TanStack Router/Query, shadcn/ui and date-fns. +- FullCalendar 7.1 with its official Monarch shadcn registry components and the CTHIT blue/orange palette. +- Vite+ 0.3: Vite/Rolldown builds, Vitest tests, type-aware Oxlint and Oxfmt through `vp`. +- Express 5, Apollo Server 5, GraphQL 16 and Prisma 7 with the PostgreSQL driver adapter. +- Gamma OpenID Connect authentication; Redis-backed signed sessions. -- [@molleer](https://github.com/molleer/) David 'Mölle' Möller -- [@x183](https://github.com/x183/) Oscar 'saxen' Palm -- [@Supergamer1337](https://github.com/Supergamer1337/) Felix 'sonic' Bjerhem Aronsson +GraphQL 16 is retained because Apollo Server 5 declares support for that major. +Prisma CLI and client stay on the same stable 7.10 release; the CLI's newer release-candidate tag is not mixed with the stable client. +Node's LTS line and matching Node types are deliberate runtime pins. -## Requirements +## Development -- [Docker](https://www.docker.com/) -- [NodeJs](https://nodejs.org/en/) -- npm -- bash +Install the pinned Node version, then from the repository root: + +```sh +corepack enable +corepack prepare pnpm@12.3.4 --activate +pnpm install --frozen-lockfile +pnpm codegen +pnpm --dir backend exec prisma generate +``` -## Setup +Install scripts are explicitly approved in `pnpm-workspace.yaml`. Review new blocked +scripts individually; do not globally enable dependency build scripts. -Dependencies +The fastest isolated local environment includes real Gamma and test-only identities: ```sh -docker compose up -d +pnpm exec playwright install chromium +pnpm e2e:dev ``` -Backend +Docker must be running with sufficient disk space. Open the printed BookIT URL. +Ctrl+C cleans up only that environment's processes, network and containers. +See [e2e/README.md](e2e/README.md) for roles and the service-version matrix. + +For an existing local Gamma installation: + +1. Start local backing services with `docker compose up -d db redis`. +2. Copy `backend/.env.example` to `backend/.env` and replace its placeholders. +3. Create a Gamma OAuth client with redirect URL `http://localhost:8080/api/callback`; + configure its client ID/secret and CLIENT API key in the environment. +4. Generate a strong session secret with `openssl rand -hex 32`. +5. Check that `DATABASE_URL` points only at your local database, then apply the schema + explicitly with `pnpm --dir backend migrate`. +6. Run `pnpm --dir frontend dev` and `pnpm --dir backend dev` in separate terminals. +7. Open `http://localhost:8080`, the backend's same-origin frontend proxy. + +The browser API is `/api/graphql/v1`; login/logout/callback are under `/api`. +`/api/health` is an unauthenticated readiness endpoint. Gamma's client-specific +`admin` authority grants administration. Active (non-alumni) Gamma groups grant +booking rights. Do not use production credentials or data for local tests. + +## Checks ```sh -cd backend -npm i -source dev_setup.sh -npm run dev +pnpm codegen +pnpm --dir backend exec prisma generate +pnpm typecheck +pnpm check +pnpm test +pnpm build +pnpm audit --audit-level low ``` -Go to http://localhost:8080 if you want to use the GraphiQL UI to build queries +`pnpm format` formats source/configuration with Oxfmt. `pnpm check` runs formatting, +type-aware lint and type checking; React hook rules apply only to frontend code, +not Playwright's unrelated fixture callbacks. Generated files are not hand-edited. + +The source of the API contract is `backend/src/schemas/v1/*.gql`. The supported +GraphQL Codegen core API generates backend resolver signatures and typed frontend +operation documents from this schema and `frontend/src/api/operations.graphql`. +Both generated outputs are committed; CI rejects stale output. + +The redesigned interface keeps the original room filters, multi-room booking colors, +calendar selection and drag-to-move behavior, booking details dialog, group selection, +consent fields, Swedish/English labels, and sortable rules table. Room filters remain +compact so the calendar is the main view; decorative headings are intentionally omitted. -Frontend +## Browser verification + +Complete a manual browser smoke pass before running the automated flows: + +```sh +pnpm e2e:dev +# Open the printed URL and check booking, editing/deletion, rules, language/mobile. +# Stop the manual environment, then: +pnpm test:e2e +``` + +Four separate Playwright specs cover booking persistence, calendar filters and +navigation, administrator rules, and group/outsider authorization. The extended +test fixture authenticates a fresh browser context by default, with configurable +admin/member/outsider identities. It uses real Gamma rather than mocked login. +One `e2e/compose.ts` owns all Testcontainers orchestration and separate databases. + +CI enforces contracts, types, lint/format, unit/API tests, builds, dependency audit, +the browser suite, and container builds. Browser failure artifacts are retained. +Successful main builds publish the existing GHCR frontend/backend image names. +Actions are SHA-pinned and Dependabot tracks future updates. + +## Containers and deployment + +Build from the repository root: ```sh -cd frontend -source dev_setup.sh -npm i -npm start +docker build -f backend/Dockerfile -t bookit-backend:local . +docker build -f frontend/Dockerfile -t bookit-frontend:local . +docker build -f db-scripts/Dockerfile -t bookit-cleanup:local db-scripts ``` -Go to http://localhost:3000 to view the website +Backend port: 8080. Frontend Nginx port: 80. Configure the backend's +`FRONTEND_URL` to reach the frontend container. Expose the backend through the +trusted TLS reverse proxy so browser assets and API share an origin. Enable +`TRUST_PROXY=1` only when exactly one trusted proxy is in front. + +Container startup no longer applies schema changes automatically. Before a release, +back up the database, review the Prisma schema diff and apply the approved schema +change explicitly using `pnpm --dir backend migrate` from a release checkout +(or `./node_modules/.bin/prisma db push` inside the backend image). +This repository still uses Prisma schema push, not a versioned migration history. +Never add `--accept-data-loss` as an unattended startup workaround. + +Session cookies are now signed; existing users will need to sign in again after +deployment. GraphQL dates now serialize explicitly as ISO strings. Mutation inputs +and IDs are non-null, and `deleteRule` returns the same localized error-or-null +contract as other mutations. Deploy frontend and backend together. + +Booking phone numbers are returned only to their original author or an administrator. +An authorized group edit may omit the phone number to preserve it; new bookings still +require one. Edits preserve the original author, and overlapping booking writes are +checked within a serializable transaction. + +The privacy cleanup retains the existing SQL that clears phone numbers and +booking-user IDs 14 days after an event. Its unsupported Jobber image was replaced +with a small PostgreSQL-client container: cleanup runs on startup and every 24 +hours, retrying failures after one hour. It must target the correct application +database and be monitored separately. + +## Security and deferred infrastructure upgrades + +**PostgreSQL 12 and Redis 5 server upgrades are intentionally separate PRs.** +The development and E2E BookIT images pin PostgreSQL 12.22 and Redis 5.0.14 for +compatibility testing; pinning them does not restore security support. Confirm +actual production versions independently. Neither server should be publicly +exposed, and these follow-ups should not be treated as optional indefinitely. + +The modern Redis client explicitly uses RESP2 for Redis 5 compatibility. +Gamma's E2E PostgreSQL and Redis instances are separate from BookIT's; their pins +match the known working Gamma integration reference, not a production upgrade. + +Scoped dependency overrides address Prisma's vulnerable deepmerge-ts/MySQL2 +transitives and the deprecated Glob version pulled by Testcontainers' archive +helper. Remove those overrides once their parents support patched releases. +Code generation uses local schema files only, avoiding unused remote loaders and +their deprecated fetch polyfills. + +A clean package audit means no known advisories were reported for the lockfile +at that time. It does not certify application security or the deferred container +images. CI keeps auditing future runs; service image scanning and production +upgrade reviews remain necessary. + +The inherited rule schema has fixed February 2022 defaults for its creation/update +timestamps. The details view displays the stored values; correcting those defaults +and deciding how to repair historical metadata needs a separate schema review. -## Technologies +## Contributors -- [Docker](https://www.docker.com/) to easily set up an development environment and launch the service in production -- [PostgreSQL](https://www.postgresql.org/) to store service data -- [NodeJs](https://nodejs.org/en/) used to power the backend and frontend -- [TypeScript](https://www.typescriptlang.org/) to enforce strict typing -- [ExpressJs](http://expressjs.com/) to create backend endpoints -- [Prisma](https://www.prisma.io/docs/) to query the database and manage database migrations -- [GraphQL](https://graphql.org/) to create a flexible web API -- [ReactJs](https://reactjs.org/) framework to create the frontend -- [Redis](https://redis.io/) to store user sessions +- [@molleer](https://github.com/molleer/) David 'Mölle' Möller +- [@x183](https://github.com/x183/) Oscar 'saxen' Palm +- [@Supergamer1337](https://github.com/Supergamer1337/) Felix 'sonic' Bjerhem Aronsson diff --git a/backend/.env.example b/backend/.env.example index 9a90760..7e70eb1 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -4,14 +4,13 @@ DB_HOST=localhost DB_PORT=5432 DB_NAME=bookit -DATABASE_URL=postgres://$DB_USER:$DB_PASS@$DB_HOST:$DB_PORT/$DB_NAME +DATABASE_URL=postgresql://postgres:password@127.0.0.1:5432/bookit ISSUER_BASE_URL=https://auth.chalmers.it BASE_URL=http://localhost:8080 -CLIENT_ID=[client id] -CLIENT_SECRET=[client secret] -API_KEY=[api-key id]:[api-key] # pre-shared token -SECRET=some_random_long_string +CLIENT_ID=replace-with-gamma-client-id +CLIENT_SECRET=replace-with-gamma-client-secret +API_KEY=replace-with-client-api-key-id:replace-with-client-api-key FRONTEND_URL=http://localhost:3001 # URL to the frontend used for proxying @@ -19,7 +18,9 @@ REDIS_HOST=localhost REDIS_PORT=6379 REDIS_PASS="" -SESSION_SECRET=secret -GRAPHIQL=true -MOCK=true -ADMIN_AUTHORITY=bookit-admin \ No newline at end of file +# Generate locally with: openssl rand -hex 32 +SESSION_SECRET=replace-with-at-least-32-random-characters +PORT=8080 +TZ=Europe/Stockholm +# Enable only behind exactly one trusted reverse proxy (TLS termination). +TRUST_PROXY=0 diff --git a/backend/.eslintrc.yml b/backend/.eslintrc.yml deleted file mode 100644 index 415d0d9..0000000 --- a/backend/.eslintrc.yml +++ /dev/null @@ -1,13 +0,0 @@ -env: - browser: true - es2021: true -extends: - - standard -parser: "@typescript-eslint/parser" -parserOptions: - ecmaVersion: 12 - sourceType: module -plugins: - - "@typescript-eslint" -rules: - semi: "always" diff --git a/backend/.prettierrc b/backend/.prettierrc deleted file mode 100644 index 6f47b18..0000000 --- a/backend/.prettierrc +++ /dev/null @@ -1,12 +0,0 @@ -{ - "tabWidth": 2, - "printWidth": 80, - "semi": true, - "arrowParens": "avoid", - "bracketSpacing": true, - "useTabs": false, - "trailingComma": "all", - "jsxBracketSameLine": false, - "singleQuote": false, - "jsxSingleQuote": false -} \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile index e848c85..c8105ad 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,19 +1,33 @@ -FROM node:16 as build +# Build from the repository root: docker build -f backend/Dockerfile . +FROM node:24.19.0-bookworm-slim AS base +ENV COREPACK_HOME=/opt/corepack +RUN apt-get update && apt-get install -y --no-install-recommends openssl ca-certificates && apt-get clean +RUN corepack enable && corepack prepare pnpm@12.3.4 --activate && pnpm --version +WORKDIR /workspace -COPY package.json package-lock.json ./ -COPY ./prisma/ . -RUN npm install +FROM base AS build +ENV CI=true +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ +COPY backend/package.json ./backend/package.json +COPY frontend/package.json ./frontend/package.json +COPY backend/prisma ./backend/prisma +COPY backend/prisma.config.ts ./backend/prisma.config.ts +RUN pnpm install --frozen-lockfile --filter backend +COPY backend ./backend +RUN pnpm --dir backend exec prisma generate \ + && pnpm --dir backend build \ + && node -e 'require("node:fs").cpSync("backend/src/schemas", "backend/build/schemas", { recursive: true })' \ + && pnpm --filter backend deploy --prod --legacy /app \ + && node -e 'require("node:fs").copyFileSync("pnpm-workspace.yaml", "/app/pnpm-workspace.yaml")' -COPY . . -RUN npm run build +# Deployment creates a fresh node_modules layout; generate the client there. +WORKDIR /app +RUN ./node_modules/.bin/prisma generate -FROM node:16 - -COPY --from=build ./build ./build/ -COPY --from=build ./src/schemas ./build/schemas -COPY --from=build ./prisma ./prisma/ -COPY --from=build ./startup.sh ./package.json ./package-lock.json ./ - -RUN npm install --production - -CMD sh ./startup.sh +FROM base AS runtime +ENV NODE_ENV=production TZ=Europe/Stockholm +WORKDIR /app +COPY --from=build --chown=node:node /app ./ +USER node +EXPOSE 8080 +CMD ["sh", "./startup.sh"] diff --git a/backend/dev_setup.sh b/backend/dev_setup.sh index 5ed571c..ae22989 100755 --- a/backend/dev_setup.sh +++ b/backend/dev_setup.sh @@ -6,4 +6,5 @@ export DB_NAME=bookit export DATABASE_URL=postgres://$DB_USER:$DB_PASS@$DB_HOST:$DB_PORT/$DB_NAME -npm run migrate +# Apply the schema explicitly to this local database when ready: +# pnpm --dir backend migrate diff --git a/backend/package-lock.json b/backend/package-lock.json deleted file mode 100644 index b267259..0000000 --- a/backend/package-lock.json +++ /dev/null @@ -1,9418 +0,0 @@ -{ - "name": "backend", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "backend", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "@graphql-tools/load-files": "^6.3.2", - "@graphql-tools/merge": "^7.0.0", - "@graphql-tools/schema": "^8.0.3", - "@prisma/client": "^5.9.0-dev.58", - "axios": "^0.21.1", - "connect-redis": "^6.0.0", - "dotenv": "^16.4.5", - "express": "^4.17.1", - "express-graphql": "^0.12.0", - "express-http-proxy": "^2.0.0", - "express-openid-connect": "^2.17.1", - "express-session": "^1.17.2", - "graphql": "^15.5.1", - "js-base64": "^3.6.1", - "qs": "^6.10.1", - "redis": "^3.1.2", - "ts-node": "^10.9.1" - }, - "devDependencies": { - "@testdeck/mocha": "^0.1.2", - "@types/axios": "^0.14.0", - "@types/chai": "^4.2.21", - "@types/connect-redis": "^0.0.17", - "@types/express": "^4.17.13", - "@types/express-http-proxy": "^1.6.6", - "@types/express-session": "^1.17.4", - "@types/mocha": "^9.0.0", - "@types/node": "^16.4.13", - "@types/passport": "^1.0.7", - "@types/redis": "^2.8.31", - "@typescript-eslint/eslint-plugin": "^4.29.1", - "@typescript-eslint/parser": "^4.29.1", - "chai": "^4.3.4", - "eslint": "^7.32.0", - "eslint-config-standard": "^16.0.3", - "eslint-plugin-import": "^2.24.0", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^5.1.0", - "mocha": "^9.0.3", - "prisma": "^5.9.0-dev.58", - "ts-node-dev": "^2.0.0", - "typescript": "^4.9.5" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", - "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/@eslint/eslintrc/node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@graphql-tools/load-files": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/load-files/-/load-files-6.3.2.tgz", - "integrity": "sha512-3mgwEKZ8yy7CD/uVs9yeXR3r+GwjlTKRG5bC75xdJFN8WbzbcHjIJiTXfWSAYqbfSTam0hWnRdWghagzFSo5kQ==", - "dependencies": { - "globby": "11.0.3", - "tslib": "~2.1.0", - "unixify": "1.0.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/@graphql-tools/merge": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-7.0.0.tgz", - "integrity": "sha512-u7TTwKQ7cybAkn6snYPRg3um/C2u690wlD8TgHITAmGQDAExN/yipSSBgu4rXWopsPLsY0G30mJ8tOWToZVE1w==", - "dependencies": { - "@graphql-tools/schema": "^8.0.3", - "@graphql-tools/utils": "8.0.2", - "tslib": "~2.3.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/@graphql-tools/merge/node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" - }, - "node_modules/@graphql-tools/schema": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-8.0.3.tgz", - "integrity": "sha512-ufJH7r/RcetVPd3kKCZ16/JTRkOX8aB1yGbYnUjqWEIdYEZc3Fpg7AVlcliu2JlvwR+WSNlgWn2QK76QCsFFdA==", - "dependencies": { - "@graphql-tools/merge": "7.0.0", - "@graphql-tools/utils": "8.0.2", - "tslib": "~2.3.0", - "value-or-promise": "1.0.10" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/@graphql-tools/schema/node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" - }, - "node_modules/@graphql-tools/utils": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.0.2.tgz", - "integrity": "sha512-gzkavMOgbhnwkHJYg32Adv6f+LxjbQmmbdD5Hty0+CWxvaiuJq+nU6tzb/7VSU4cwhbNLx/lGu2jbCPEW1McZQ==", - "dependencies": { - "tslib": "~2.3.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/@graphql-tools/utils/node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" - }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", - "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", - "dev": true - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@panva/asn1.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", - "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/@prisma/client": { - "version": "5.9.0-dev.58", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.9.0-dev.58.tgz", - "integrity": "sha512-Zl3uAxBOjOQxZaRt7ltTEMXxIhyLZprrqXABWPIQzovoCe7EfG6PbjoholfdCYv4loos5j1yeUC6WUX8Gc11Cg==", - "hasInstallScript": true, - "engines": { - "node": ">=16.13" - }, - "peerDependencies": { - "prisma": "*" - }, - "peerDependenciesMeta": { - "prisma": { - "optional": true - } - } - }, - "node_modules/@prisma/debug": { - "version": "5.9.0-dev.58", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.9.0-dev.58.tgz", - "integrity": "sha512-0aAYOY2YX90Dmtp2icENmO4qozCZEqpxkxtA/QqBBsysk5WHuC1ET6jPUUf58JwNbmWznaVbJhdIls4nD7kdRw==", - "devOptional": true - }, - "node_modules/@prisma/engines": { - "version": "5.9.0-dev.58", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.9.0-dev.58.tgz", - "integrity": "sha512-KdkIT/ZdudNYLgQxesIMkQjtYzV4gGCDxS+XrQF7sFaxRUH5EveFntTagVni3PCzQ4aK6jmSznIoJP33n2Pztw==", - "devOptional": true, - "hasInstallScript": true, - "dependencies": { - "@prisma/debug": "5.9.0-dev.58", - "@prisma/engines-version": "5.9.0-27.6248b507d344261d8c98c3096bd082ad09b3750b", - "@prisma/fetch-engine": "5.9.0-dev.58", - "@prisma/get-platform": "5.9.0-dev.58" - } - }, - "node_modules/@prisma/engines-version": { - "version": "5.9.0-27.6248b507d344261d8c98c3096bd082ad09b3750b", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.9.0-27.6248b507d344261d8c98c3096bd082ad09b3750b.tgz", - "integrity": "sha512-CdAyHsvdlgDofbZOON4YdNNPpadXBJjAFuYfSpEU6SPQbHSIjQrmXj6W7wyAx7/1s3WI91XgQ70IBW5+BBQrtQ==", - "devOptional": true - }, - "node_modules/@prisma/fetch-engine": { - "version": "5.9.0-dev.58", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.9.0-dev.58.tgz", - "integrity": "sha512-AOZn87wjlc2gpSv0b+NapToFCEy7Y1CiF5iDYQ0GXplb3t+WZDOufis524rwHn2SUsTSC1UqsOfjbqwOT/CxeQ==", - "devOptional": true, - "dependencies": { - "@prisma/debug": "5.9.0-dev.58", - "@prisma/engines-version": "5.9.0-27.6248b507d344261d8c98c3096bd082ad09b3750b", - "@prisma/get-platform": "5.9.0-dev.58" - } - }, - "node_modules/@prisma/get-platform": { - "version": "5.9.0-dev.58", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.9.0-dev.58.tgz", - "integrity": "sha512-UO6QXFht6fIDZdWtNryrlWgVvNxoZG4CtBnH+Hex/qCOAHnF70L6wgItRbAwifNiFXN7wxW3LgVtTlLZ0R81yg==", - "devOptional": true, - "dependencies": { - "@prisma/debug": "5.9.0-dev.58" - } - }, - "node_modules/@sideway/address": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", - "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" - }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "dependencies": { - "defer-to-connect": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@testdeck/core": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@testdeck/core/-/core-0.1.2.tgz", - "integrity": "sha512-rggcFQqSejqtkqK1JcwZE/utD4oNsM1JMHwrYxH1PKYx0uL2cMs0Q4zMVLKw0oacIASCfkIUSdXx7rDNdCDlvA==", - "dev": true - }, - "node_modules/@testdeck/mocha": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@testdeck/mocha/-/mocha-0.1.2.tgz", - "integrity": "sha512-yn3OkFUlO9EkdBkDV08bPV0r26U2FC/8KvU9FdiS0ligOsjB5Ry4sp3eUQJAFxjrGJBpih0t7rZ/GzdsgCv/EQ==", - "dev": true, - "dependencies": { - "@testdeck/core": "^0.1.2" - }, - "bin": { - "testdeck-watch": "bin/watch" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", - "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==" - }, - "node_modules/@types/axios": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.14.0.tgz", - "integrity": "sha1-7CMA++fX3d1+udOr+HmZlkyvzkY=", - "deprecated": "This is a stub types definition for axios (https://github.com/mzabriskie/axios). axios provides its own type definitions, so you don't need @types/axios installed!", - "dev": true, - "dependencies": { - "axios": "*" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz", - "integrity": "sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg==", - "dev": true, - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/cacheable-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", - "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", - "dependencies": { - "@types/http-cache-semantics": "*", - "@types/keyv": "^3.1.4", - "@types/node": "*", - "@types/responselike": "^1.0.0" - } - }, - "node_modules/@types/chai": { - "version": "4.2.21", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.21.tgz", - "integrity": "sha512-yd+9qKmJxm496BOV9CMNaey8TWsikaZOwMRwPHQIjcOJM9oV+fi9ZMNw3JsVnbEEbo2gRTDnGEBv8pjyn67hNg==", - "dev": true - }, - "node_modules/@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect-redis": { - "version": "0.0.17", - "resolved": "https://registry.npmjs.org/@types/connect-redis/-/connect-redis-0.0.17.tgz", - "integrity": "sha512-6apVqZFTMspIWwjGPfip7fKP+6Sl3M589DuYuYtzyc+Fzn2fnZqtQQ2o/UfwDlLh6zxq0RAnFbW3Mx1+2Nuh0w==", - "dev": true, - "dependencies": { - "@types/express": "*", - "@types/express-session": "*", - "@types/ioredis": "*", - "@types/redis": "*" - } - }, - "node_modules/@types/express": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", - "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", - "dev": true, - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-http-proxy": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/@types/express-http-proxy/-/express-http-proxy-1.6.6.tgz", - "integrity": "sha512-J8ZqHG76rq1UB716IZ3RCmUhg406pbWxsM3oFCFccl5xlWUPzoR4if6Og/cE4juK8emH0H9quZa5ltn6ZdmQJg==", - "dev": true, - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.24", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz", - "integrity": "sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA==", - "dev": true, - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, - "node_modules/@types/express-session": { - "version": "1.17.4", - "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.4.tgz", - "integrity": "sha512-7cNlSI8+oOBUHTfPXMwDxF/Lchx5aJ3ho7+p9jJZYVg9dVDJFh3qdMXmJtRsysnvS+C6x46k9DRYmrmCkE+MVg==", - "dev": true, - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/http-cache-semantics": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==" - }, - "node_modules/@types/ioredis": { - "version": "4.27.1", - "resolved": "https://registry.npmjs.org/@types/ioredis/-/ioredis-4.27.1.tgz", - "integrity": "sha512-+JichYPCtVapxi5Z+7V/8Uhn+4Piz+rkb03JQTQ0kW0pkV71+bmzSTkpwmIqa7FfWIszp0VbRyN8gfgWDNQMdA==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", - "dev": true - }, - "node_modules/@types/keyv": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/mime": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", - "dev": true - }, - "node_modules/@types/mocha": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.0.0.tgz", - "integrity": "sha512-scN0hAWyLVAvLR9AyW7HoFF5sJZglyBsbPuHO4fv7JRvfmPBMfp1ozWqOf/e4wwPNxezBZXRfWzMb6iFLgEVRA==", - "dev": true - }, - "node_modules/@types/node": { - "version": "16.4.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.4.13.tgz", - "integrity": "sha512-bLL69sKtd25w7p1nvg9pigE4gtKVpGTPojBFLMkGHXuUgap2sLqQt2qUnqmVCDfzGUL0DRNZP+1prIZJbMeAXg==" - }, - "node_modules/@types/passport": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.7.tgz", - "integrity": "sha512-JtswU8N3kxBYgo+n9of7C97YQBT+AYPP2aBfNGTzABqPAZnK/WOAaKfh3XesUYMZRrXFuoPc2Hv0/G/nQFveHw==", - "dev": true, - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true - }, - "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", - "dev": true - }, - "node_modules/@types/redis": { - "version": "2.8.31", - "resolved": "https://registry.npmjs.org/@types/redis/-/redis-2.8.31.tgz", - "integrity": "sha512-daWrrTDYaa5iSDFbgzZ9gOOzyp2AJmYK59OlG/2KGBgYWF3lfs8GDKm1c//tik5Uc93hDD36O+qLPvzDolChbA==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/responselike": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", - "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.13.10", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", - "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", - "dev": true, - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=", - "dev": true - }, - "node_modules/@types/strip-json-comments": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", - "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", - "dev": true - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.29.1.tgz", - "integrity": "sha512-AHqIU+SqZZgBEiWOrtN94ldR3ZUABV5dUG94j8Nms9rQnHFc8fvDOue/58K4CFz6r8OtDDc35Pw9NQPWo0Ayrw==", - "dev": true, - "dependencies": { - "@typescript-eslint/experimental-utils": "4.29.1", - "@typescript-eslint/scope-manager": "4.29.1", - "debug": "^4.3.1", - "functional-red-black-tree": "^1.0.1", - "regexpp": "^3.1.0", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^4.0.0", - "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@typescript-eslint/experimental-utils": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.29.1.tgz", - "integrity": "sha512-kl6QG6qpzZthfd2bzPNSJB2YcZpNOrP6r9jueXupcZHnL74WiuSjaft7WSu17J9+ae9zTlk0KJMXPUj0daBxMw==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.29.1", - "@typescript-eslint/types": "4.29.1", - "@typescript-eslint/typescript-estree": "4.29.1", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.29.1.tgz", - "integrity": "sha512-3fL5iN20hzX3Q4OkG7QEPFjZV2qsVGiDhEwwh+EkmE/w7oteiOvUNzmpu5eSwGJX/anCryONltJ3WDmAzAoCMg==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "4.29.1", - "@typescript-eslint/types": "4.29.1", - "@typescript-eslint/typescript-estree": "4.29.1", - "debug": "^4.3.1" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser/node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.29.1.tgz", - "integrity": "sha512-Hzv/uZOa9zrD/W5mftZa54Jd5Fed3tL6b4HeaOpwVSabJK8CJ+2MkDasnX/XK4rqP5ZTWngK1ZDeCi6EnxPQ7A==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "4.29.1", - "@typescript-eslint/visitor-keys": "4.29.1" - }, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.29.1.tgz", - "integrity": "sha512-Jj2yu78IRfw4nlaLtKjVaGaxh/6FhofmQ/j8v3NXmAiKafbIqtAPnKYrf0sbGjKdj0hS316J8WhnGnErbJ4RCA==", - "dev": true, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.29.1.tgz", - "integrity": "sha512-lIkkrR9E4lwZkzPiRDNq0xdC3f2iVCUjw/7WPJ4S2Sl6C3nRWkeE1YXCQ0+KsiaQRbpY16jNaokdWnm9aUIsfw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "4.29.1", - "@typescript-eslint/visitor-keys": "4.29.1", - "debug": "^4.3.1", - "globby": "^11.0.3", - "is-glob": "^4.0.1", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.29.1.tgz", - "integrity": "sha512-zLqtjMoXvgdZY/PG6gqA73V8BjqPs4af1v2kiiETBObp+uC6gRYnJLmJHxC0QyUrrHDLJPIWNYxoBV3wbcRlag==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "4.29.1", - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true - }, - "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==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.4.1.tgz", - "integrity": "sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.1.1.tgz", - "integrity": "sha512-FbJdceMlPHEAWJOILDk1fXD8lnTlEIWFkqtfk+MvmL5q/qlHfN7GEHcsFZWt/Tea9jRNPWUZG4G976nqAAmU9w==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" - }, - "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, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "node_modules/array-includes": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz", - "integrity": "sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2", - "get-intrinsic": "^1.1.1", - "is-string": "^1.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz", - "integrity": "sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "dependencies": { - "follow-redirects": "^1.14.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 - }, - "node_modules/base64url": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", - "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "engines": { - "node": ">=10.6.0" - } - }, - "node_modules/cacheable-request": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", - "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/chai": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", - "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", - "dev": true, - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "engines": { - "node": ">=6" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", - "dependencies": { - "mimic-response": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "node_modules/connect-redis": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-6.0.0.tgz", - "integrity": "sha512-6eGEAAPHYvcfbRNCMmPzBIjrqRWLw7at9lCUH4G6NQ8gwWDJelaUmFNOqPIhehbw941euVmIuqWsaWiKXfb+5g==", - "engines": { - "node": ">=12" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "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==", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response/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==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "engines": { - "node": ">=10" - } - }, - "node_modules/define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "dependencies": { - "object-keys": "^1.0.12" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/denque": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", - "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dotenv": { - "version": "16.4.5", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", - "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dynamic-dedupe": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", - "integrity": "sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE=", - "dev": true, - "dependencies": { - "xtend": "^4.0.0" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-abstract": { - "version": "1.18.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.5.tgz", - "integrity": "sha512-DDggyJLoS91CkJjgauM5c0yZMjiD1uK3KcaCeAmffGwZ+ODWzOkPN4QwRbsK5DOFf06fywmyLci3ZD8jLGhVYA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.2", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.3", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.3", - "is-string": "^1.0.6", - "object-inspect": "^1.11.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-standard": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-16.0.3.tgz", - "integrity": "sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg==", - "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" - } - ], - "peerDependencies": { - "eslint": "^7.12.1", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^4.2.1 || ^5.0.0" - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.5.tgz", - "integrity": "sha512-XMoPKjSpXbkeJ7ZZ9icLnJMTY5Mc1kZbCakHquaFsXPpyWOwK0TK6CODO+0ca54UoM9LKOxyUNnoVZRl8TeaAg==", - "dev": true, - "dependencies": { - "debug": "^3.2.7", - "resolve": "^1.20.0" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/eslint-module-utils": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.2.tgz", - "integrity": "sha512-QG8pcgThYOuqxupd06oYTZoNOGaUdTY1PqK+oS6ElF6vs4pBdk/aYxFVQQXzcrAqp9m7cl7lb2ubazX+g16k2Q==", - "dev": true, - "dependencies": { - "debug": "^3.2.7", - "pkg-dir": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-module-utils/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/eslint-plugin-es": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", - "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", - "dev": true, - "dependencies": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - }, - "engines": { - "node": ">=8.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=4.19.1" - } - }, - "node_modules/eslint-plugin-es/node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.24.0.tgz", - "integrity": "sha512-Kc6xqT9hiYi2cgybOc0I2vC9OgAYga5o/rAFinam/yF/t5uBqxQbauNPMC6fgb640T/89P0gFoO27FOilJ/Cqg==", - "dev": true, - "dependencies": { - "array-includes": "^3.1.3", - "array.prototype.flat": "^1.2.4", - "debug": "^2.6.9", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.5", - "eslint-module-utils": "^2.6.2", - "find-up": "^2.0.0", - "has": "^1.0.3", - "is-core-module": "^2.4.0", - "minimatch": "^3.0.4", - "object.values": "^1.1.3", - "pkg-up": "^2.0.0", - "read-pkg-up": "^3.0.0", - "resolve": "^1.20.0", - "tsconfig-paths": "^3.9.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-node": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", - "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", - "dev": true, - "dependencies": { - "eslint-plugin-es": "^3.0.0", - "eslint-utils": "^2.0.0", - "ignore": "^5.1.1", - "minimatch": "^3.0.4", - "resolve": "^1.10.1", - "semver": "^6.1.0" - }, - "engines": { - "node": ">=8.10.0" - }, - "peerDependencies": { - "eslint": ">=5.16.0" - } - }, - "node_modules/eslint-plugin-node/node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-plugin-node/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-plugin-node/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-plugin-promise": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.1.0.tgz", - "integrity": "sha512-NGmI6BH5L12pl7ScQHbg7tvtk4wPxxj8yPHH47NvSmMtFneC077PSeY3huFj06ZWZvtbfxSPt3RuOQD5XcR4ng==", - "dev": true, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "peerDependencies": { - "eslint": "^7.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/eslint/node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint/node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/eslint/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/espree/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express-graphql": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/express-graphql/-/express-graphql-0.12.0.tgz", - "integrity": "sha512-DwYaJQy0amdy3pgNtiTDuGGM2BLdj+YO2SgbKoLliCfuHv3VVTt7vNG/ZqK2hRYjtYHE2t2KB705EU94mE64zg==", - "dependencies": { - "accepts": "^1.3.7", - "content-type": "^1.0.4", - "http-errors": "1.8.0", - "raw-body": "^2.4.1" - }, - "engines": { - "node": ">= 10.x" - }, - "peerDependencies": { - "graphql": "^14.7.0 || ^15.3.0" - } - }, - "node_modules/express-graphql/node_modules/http-errors": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz", - "integrity": "sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express-http-proxy": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/express-http-proxy/-/express-http-proxy-2.0.0.tgz", - "integrity": "sha512-TXxcPFTWVUMSEmyM6iX2sT/JtmqhqngTq29P+eXTVFdtxZrTmM8THUYK59rUXiln0FfPGvxEpGRnVrgvHksXDw==", - "dependencies": { - "debug": "^3.0.1", - "es6-promise": "^4.1.1", - "raw-body": "^2.3.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/express-http-proxy/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/express-http-proxy/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/express-openid-connect": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/express-openid-connect/-/express-openid-connect-2.17.1.tgz", - "integrity": "sha512-5pVK6PNV09x6UN29R9Mer0XF3hwQq2HxiFsjZvLuIQ9ezeTUGbqrefzBOpzciz1S/1WWVaVPDIcj4EBpD8WB3Q==", - "dependencies": { - "base64url": "^3.0.1", - "clone": "^2.1.2", - "cookie": "^0.5.0", - "debug": "^4.3.4", - "futoin-hkdf": "^1.5.1", - "http-errors": "^1.8.1", - "joi": "^17.7.0", - "jose": "^2.0.6", - "on-headers": "^1.0.2", - "openid-client": "^4.9.1", - "url-join": "^4.0.1" - }, - "engines": { - "node": "^10.19.0 || >=12.0.0 < 13 || >=13.7.0 < 14 || >= 14.2.0" - }, - "peerDependencies": { - "express": ">= 4.17.0" - } - }, - "node_modules/express-openid-connect/node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/express-openid-connect/node_modules/http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express-openid-connect/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/express-openid-connect/node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/express-session": { - "version": "1.17.2", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.2.tgz", - "integrity": "sha512-mPcYcLA0lvh7D4Oqr5aNJFMtBMKPLl++OKKxkHzZ0U0oDq1rpKBnkR5f5vCHR26VeArlTOEF9td4x5IjICksRQ==", - "dependencies": { - "cookie": "0.4.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-headers": "~1.0.2", - "parseurl": "~1.3.3", - "safe-buffer": "5.2.1", - "uid-safe": "~2.1.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/express-session/node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express-session/node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "node_modules/fastq": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.1.tgz", - "integrity": "sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw==", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "dependencies": { - "locate-path": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", - "dev": true - }, - "node_modules/follow-redirects": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", - "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "node_modules/futoin-hkdf": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/futoin-hkdf/-/futoin-hkdf-1.5.3.tgz", - "integrity": "sha512-SewY5KdMpaoCeh7jachEWFsh1nNlaDjNHZXWqL5IGwtpEYHTgkr2+AMCgNwKWkcc0wpSYrZfR7he4WdmHFtDxQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/globals": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.10.0.tgz", - "integrity": "sha512-piHC3blgLGFjvOuMmWZX60f+na1lXFDhQXBf1UYp2fXPXqvEUbOhNwi6BsQ0bQishwedgnjkwv1d9zKf+MWw3g==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", - "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=10.19.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", - "dev": true - }, - "node_modules/graphql": { - "version": "15.5.1", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.5.1.tgz", - "integrity": "sha512-FeTRX67T3LoE3LWAxxOlW2K3Bz+rMYAC18rRguK4wgXaTZMiJwSUwDmPFo3UadAKbzirKIg5Qy+sNJXbpPRnQw==", - "engines": { - "node": ">= 10.x" - } - }, - "node_modules/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true, - "engines": { - "node": ">=4.x" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-errors/node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-errors/node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "node_modules/is-bigint": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.3.tgz", - "integrity": "sha512-ZU538ajmYJmzysE5yU4Y7uIrPQ2j704u+hXFiIPQExpqzzUbpe5jCPdTfmz7jXRxZdvjY3KZ3ZNenoXQovX+Dg==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.5.0.tgz", - "integrity": "sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", - "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "node_modules/joi": { - "version": "17.13.1", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.1.tgz", - "integrity": "sha512-vaBlIKCyo4FCUtCm7Eu4QZd/q02bWcxfUO6YSXAZOWF6gzcLBeba8kwotUdYJjDLW8Cz8RywsSOqiNJZW0mNvg==", - "dependencies": { - "@hapi/hoek": "^9.3.0", - "@hapi/topo": "^5.1.0", - "@sideway/address": "^4.1.5", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } - }, - "node_modules/jose": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.7.tgz", - "integrity": "sha512-5hFWIigKqC+e/lRyQhfnirrAqUdIPMB7SJRqflJaO29dW7q5DFvH1XCSTmv6PQ6pb++0k6MJlLRoS0Wv4s38Wg==", - "dependencies": { - "@panva/asn1.js": "^1.0.0" - }, - "engines": { - "node": ">=10.13.0 < 13 || >=13.7.0" - }, - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/js-base64": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.6.1.tgz", - "integrity": "sha512-Frdq2+tRRGLQUIQOgsIGSCd1VePCS2fsddTG5dTCqR0JHgltXWfsxnY0gIXPoMeRmdom6Oyq+UMOFg5suduOjQ==" - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "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, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" - }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", - "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", - "dev": true, - "dependencies": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.3", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "4.2.1", - "ms": "2.1.3", - "nanoid": "3.3.1", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "which": "2.0.2", - "workerpool": "6.2.0", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/mocha/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/mocha/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mocha/node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/mocha/node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/mocha/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/mocha/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", - "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/mocha/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/mocha/node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "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==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/object-inspect": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.values": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.4.tgz", - "integrity": "sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/oidc-token-hash": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", - "integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==", - "engines": { - "node": "^10.13.0 || >=12.0.0" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "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==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/openid-client": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-4.9.1.tgz", - "integrity": "sha512-DYUF07AHjI3QDKqKbn2F7RqozT4hyi4JvmpodLrq0HHoNP7t/AjeG/uqiBK1/N2PZSAQEThVjDLHSmJN4iqu/w==", - "dependencies": { - "aggregate-error": "^3.1.0", - "got": "^11.8.0", - "jose": "^2.0.5", - "lru-cache": "^6.0.0", - "make-error": "^1.3.6", - "object-hash": "^2.0.1", - "oidc-token-hash": "^5.0.1" - }, - "engines": { - "node": "^10.19.0 || >=12.0.0 < 13 || >=13.7.0 < 14 || >= 14.2.0" - }, - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "dependencies": { - "p-try": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "dependencies": { - "p-limit": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "dependencies": { - "find-up": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", - "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", - "dev": true, - "dependencies": { - "find-up": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prisma": { - "version": "5.9.0-dev.58", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.9.0-dev.58.tgz", - "integrity": "sha512-dAz5V9Wg0fXKKvfXreDc7ckO/sX1ZJXa3aCkGSjgoD799AJz3e6DSR4QmsTcVaHzCp0h5Ocs3Fu/BFyHjJxgCQ==", - "devOptional": true, - "hasInstallScript": true, - "dependencies": { - "@prisma/engines": "5.9.0-dev.58" - }, - "bin": { - "prisma": "build/index.js" - }, - "engines": { - "node": ">=16.13" - } - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, - "dependencies": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", - "dev": true, - "dependencies": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg/node_modules/path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/redis": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/redis/-/redis-3.1.2.tgz", - "integrity": "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==", - "dependencies": { - "denque": "^1.5.0", - "redis-commands": "^1.7.0", - "redis-errors": "^1.2.0", - "redis-parser": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-redis" - } - }, - "node_modules/redis-commands": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz", - "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==" - }, - "node_modules/redis-errors": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", - "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=", - "engines": { - "node": ">=4" - } - }, - "node_modules/redis-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", - "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=", - "dependencies": { - "redis-errors": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true, - "engines": { - "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, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "dev": true, - "dependencies": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "dependencies": { - "lowercase-keys": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/send/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz", - "integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==", - "dev": true - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/table": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", - "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", - "dev": true, - "dependencies": { - "ajv": "^8.0.1", - "lodash.clonedeep": "^4.5.0", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table/node_modules/ajv": { - "version": "8.6.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz", - "integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==", - "dev": true, - "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/table/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 - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "bin": { - "tree-kill": "cli.js" - } - }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/ts-node-dev": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz", - "integrity": "sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==", - "dev": true, - "dependencies": { - "chokidar": "^3.5.1", - "dynamic-dedupe": "^0.3.0", - "minimist": "^1.2.6", - "mkdirp": "^1.0.4", - "resolve": "^1.0.0", - "rimraf": "^2.6.1", - "source-map-support": "^0.5.12", - "tree-kill": "^1.2.2", - "ts-node": "^10.4.0", - "tsconfig": "^7.0.0" - }, - "bin": { - "ts-node-dev": "lib/bin.js", - "tsnd": "lib/bin.js" - }, - "engines": { - "node": ">=0.8.0" - }, - "peerDependencies": { - "node-notifier": "*", - "typescript": "*" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/ts-node-dev/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/tsconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", - "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", - "dev": true, - "dependencies": { - "@types/strip-bom": "^3.0.0", - "@types/strip-json-comments": "0.0.30", - "strip-bom": "^3.0.0", - "strip-json-comments": "^2.0.0" - } - }, - "node_modules/tsconfig-paths": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.10.1.tgz", - "integrity": "sha512-rETidPDgCpltxF7MjBZlAFPUHv5aHH2MymyPvh+vEyWAED4Eb/WeMbsnD/JDr4OKPOA1TssDHgIcpTN5Kh0p6Q==", - "dev": true, - "dependencies": { - "json5": "^2.2.0", - "minimist": "^1.2.0", - "strip-bom": "^3.0.0" - } - }, - "node_modules/tsconfig/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": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/uid-safe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", - "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", - "dependencies": { - "random-bytes": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/unixify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unixify/-/unixify-1.0.0.tgz", - "integrity": "sha1-OmQcjC/7zk2mg6XHDwOkYpQMIJA=", - "dependencies": { - "normalize-path": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/url-join": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", - "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/value-or-promise": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.10.tgz", - "integrity": "sha512-1OwTzvcfXkAfabk60UVr5NdjtjJ0Fg0T5+B1bhxtrOEwSH2fe8y4DnLgoksfCyd8yZCOQQHB0qLMQnwgCjbXLQ==", - "engines": { - "node": ">=12" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/workerpool": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", - "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser/node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", - "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", - "dev": true - }, - "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "requires": { - "@jridgewell/trace-mapping": "0.3.9" - } - }, - "@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@graphql-tools/load-files": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/load-files/-/load-files-6.3.2.tgz", - "integrity": "sha512-3mgwEKZ8yy7CD/uVs9yeXR3r+GwjlTKRG5bC75xdJFN8WbzbcHjIJiTXfWSAYqbfSTam0hWnRdWghagzFSo5kQ==", - "requires": { - "globby": "11.0.3", - "tslib": "~2.1.0", - "unixify": "1.0.0" - } - }, - "@graphql-tools/merge": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-7.0.0.tgz", - "integrity": "sha512-u7TTwKQ7cybAkn6snYPRg3um/C2u690wlD8TgHITAmGQDAExN/yipSSBgu4rXWopsPLsY0G30mJ8tOWToZVE1w==", - "requires": { - "@graphql-tools/schema": "^8.0.3", - "@graphql-tools/utils": "8.0.2", - "tslib": "~2.3.0" - }, - "dependencies": { - "tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" - } - } - }, - "@graphql-tools/schema": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-8.0.3.tgz", - "integrity": "sha512-ufJH7r/RcetVPd3kKCZ16/JTRkOX8aB1yGbYnUjqWEIdYEZc3Fpg7AVlcliu2JlvwR+WSNlgWn2QK76QCsFFdA==", - "requires": { - "@graphql-tools/merge": "7.0.0", - "@graphql-tools/utils": "8.0.2", - "tslib": "~2.3.0", - "value-or-promise": "1.0.10" - }, - "dependencies": { - "tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" - } - } - }, - "@graphql-tools/utils": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.0.2.tgz", - "integrity": "sha512-gzkavMOgbhnwkHJYg32Adv6f+LxjbQmmbdD5Hty0+CWxvaiuJq+nU6tzb/7VSU4cwhbNLx/lGu2jbCPEW1McZQ==", - "requires": { - "tslib": "~2.3.0" - }, - "dependencies": { - "tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" - } - } - }, - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - }, - "@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, - "@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, - "dependencies": { - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@humanwhocodes/object-schema": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", - "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", - "dev": true - }, - "@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==" - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" - }, - "@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@panva/asn1.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", - "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==" - }, - "@prisma/client": { - "version": "5.9.0-dev.58", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.9.0-dev.58.tgz", - "integrity": "sha512-Zl3uAxBOjOQxZaRt7ltTEMXxIhyLZprrqXABWPIQzovoCe7EfG6PbjoholfdCYv4loos5j1yeUC6WUX8Gc11Cg==", - "requires": {} - }, - "@prisma/debug": { - "version": "5.9.0-dev.58", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.9.0-dev.58.tgz", - "integrity": "sha512-0aAYOY2YX90Dmtp2icENmO4qozCZEqpxkxtA/QqBBsysk5WHuC1ET6jPUUf58JwNbmWznaVbJhdIls4nD7kdRw==", - "devOptional": true - }, - "@prisma/engines": { - "version": "5.9.0-dev.58", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.9.0-dev.58.tgz", - "integrity": "sha512-KdkIT/ZdudNYLgQxesIMkQjtYzV4gGCDxS+XrQF7sFaxRUH5EveFntTagVni3PCzQ4aK6jmSznIoJP33n2Pztw==", - "devOptional": true, - "requires": { - "@prisma/debug": "5.9.0-dev.58", - "@prisma/engines-version": "5.9.0-27.6248b507d344261d8c98c3096bd082ad09b3750b", - "@prisma/fetch-engine": "5.9.0-dev.58", - "@prisma/get-platform": "5.9.0-dev.58" - } - }, - "@prisma/engines-version": { - "version": "5.9.0-27.6248b507d344261d8c98c3096bd082ad09b3750b", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.9.0-27.6248b507d344261d8c98c3096bd082ad09b3750b.tgz", - "integrity": "sha512-CdAyHsvdlgDofbZOON4YdNNPpadXBJjAFuYfSpEU6SPQbHSIjQrmXj6W7wyAx7/1s3WI91XgQ70IBW5+BBQrtQ==", - "devOptional": true - }, - "@prisma/fetch-engine": { - "version": "5.9.0-dev.58", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.9.0-dev.58.tgz", - "integrity": "sha512-AOZn87wjlc2gpSv0b+NapToFCEy7Y1CiF5iDYQ0GXplb3t+WZDOufis524rwHn2SUsTSC1UqsOfjbqwOT/CxeQ==", - "devOptional": true, - "requires": { - "@prisma/debug": "5.9.0-dev.58", - "@prisma/engines-version": "5.9.0-27.6248b507d344261d8c98c3096bd082ad09b3750b", - "@prisma/get-platform": "5.9.0-dev.58" - } - }, - "@prisma/get-platform": { - "version": "5.9.0-dev.58", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.9.0-dev.58.tgz", - "integrity": "sha512-UO6QXFht6fIDZdWtNryrlWgVvNxoZG4CtBnH+Hex/qCOAHnF70L6wgItRbAwifNiFXN7wxW3LgVtTlLZ0R81yg==", - "devOptional": true, - "requires": { - "@prisma/debug": "5.9.0-dev.58" - } - }, - "@sideway/address": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", - "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, - "@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" - }, - "@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" - }, - "@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==" - }, - "@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "requires": { - "defer-to-connect": "^2.0.0" - } - }, - "@testdeck/core": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@testdeck/core/-/core-0.1.2.tgz", - "integrity": "sha512-rggcFQqSejqtkqK1JcwZE/utD4oNsM1JMHwrYxH1PKYx0uL2cMs0Q4zMVLKw0oacIASCfkIUSdXx7rDNdCDlvA==", - "dev": true - }, - "@testdeck/mocha": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@testdeck/mocha/-/mocha-0.1.2.tgz", - "integrity": "sha512-yn3OkFUlO9EkdBkDV08bPV0r26U2FC/8KvU9FdiS0ligOsjB5Ry4sp3eUQJAFxjrGJBpih0t7rZ/GzdsgCv/EQ==", - "dev": true, - "requires": { - "@testdeck/core": "^0.1.2" - } - }, - "@tsconfig/node10": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==" - }, - "@tsconfig/node12": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==" - }, - "@tsconfig/node14": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==" - }, - "@tsconfig/node16": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", - "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==" - }, - "@types/axios": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.14.0.tgz", - "integrity": "sha1-7CMA++fX3d1+udOr+HmZlkyvzkY=", - "dev": true, - "requires": { - "axios": "*" - } - }, - "@types/body-parser": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz", - "integrity": "sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg==", - "dev": true, - "requires": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "@types/cacheable-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", - "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", - "requires": { - "@types/http-cache-semantics": "*", - "@types/keyv": "^3.1.4", - "@types/node": "*", - "@types/responselike": "^1.0.0" - } - }, - "@types/chai": { - "version": "4.2.21", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.21.tgz", - "integrity": "sha512-yd+9qKmJxm496BOV9CMNaey8TWsikaZOwMRwPHQIjcOJM9oV+fi9ZMNw3JsVnbEEbo2gRTDnGEBv8pjyn67hNg==", - "dev": true - }, - "@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/connect-redis": { - "version": "0.0.17", - "resolved": "https://registry.npmjs.org/@types/connect-redis/-/connect-redis-0.0.17.tgz", - "integrity": "sha512-6apVqZFTMspIWwjGPfip7fKP+6Sl3M589DuYuYtzyc+Fzn2fnZqtQQ2o/UfwDlLh6zxq0RAnFbW3Mx1+2Nuh0w==", - "dev": true, - "requires": { - "@types/express": "*", - "@types/express-session": "*", - "@types/ioredis": "*", - "@types/redis": "*" - } - }, - "@types/express": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", - "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", - "dev": true, - "requires": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "@types/express-http-proxy": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/@types/express-http-proxy/-/express-http-proxy-1.6.6.tgz", - "integrity": "sha512-J8ZqHG76rq1UB716IZ3RCmUhg406pbWxsM3oFCFccl5xlWUPzoR4if6Og/cE4juK8emH0H9quZa5ltn6ZdmQJg==", - "dev": true, - "requires": { - "@types/express": "*" - } - }, - "@types/express-serve-static-core": { - "version": "4.17.24", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz", - "integrity": "sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA==", - "dev": true, - "requires": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, - "@types/express-session": { - "version": "1.17.4", - "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.4.tgz", - "integrity": "sha512-7cNlSI8+oOBUHTfPXMwDxF/Lchx5aJ3ho7+p9jJZYVg9dVDJFh3qdMXmJtRsysnvS+C6x46k9DRYmrmCkE+MVg==", - "dev": true, - "requires": { - "@types/express": "*" - } - }, - "@types/http-cache-semantics": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==" - }, - "@types/ioredis": { - "version": "4.27.1", - "resolved": "https://registry.npmjs.org/@types/ioredis/-/ioredis-4.27.1.tgz", - "integrity": "sha512-+JichYPCtVapxi5Z+7V/8Uhn+4Piz+rkb03JQTQ0kW0pkV71+bmzSTkpwmIqa7FfWIszp0VbRyN8gfgWDNQMdA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", - "dev": true - }, - "@types/keyv": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", - "requires": { - "@types/node": "*" - } - }, - "@types/mime": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", - "dev": true - }, - "@types/mocha": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.0.0.tgz", - "integrity": "sha512-scN0hAWyLVAvLR9AyW7HoFF5sJZglyBsbPuHO4fv7JRvfmPBMfp1ozWqOf/e4wwPNxezBZXRfWzMb6iFLgEVRA==", - "dev": true - }, - "@types/node": { - "version": "16.4.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.4.13.tgz", - "integrity": "sha512-bLL69sKtd25w7p1nvg9pigE4gtKVpGTPojBFLMkGHXuUgap2sLqQt2qUnqmVCDfzGUL0DRNZP+1prIZJbMeAXg==" - }, - "@types/passport": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.7.tgz", - "integrity": "sha512-JtswU8N3kxBYgo+n9of7C97YQBT+AYPP2aBfNGTzABqPAZnK/WOAaKfh3XesUYMZRrXFuoPc2Hv0/G/nQFveHw==", - "dev": true, - "requires": { - "@types/express": "*" - } - }, - "@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true - }, - "@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", - "dev": true - }, - "@types/redis": { - "version": "2.8.31", - "resolved": "https://registry.npmjs.org/@types/redis/-/redis-2.8.31.tgz", - "integrity": "sha512-daWrrTDYaa5iSDFbgzZ9gOOzyp2AJmYK59OlG/2KGBgYWF3lfs8GDKm1c//tik5Uc93hDD36O+qLPvzDolChbA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/responselike": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", - "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", - "requires": { - "@types/node": "*" - } - }, - "@types/serve-static": { - "version": "1.13.10", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", - "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", - "dev": true, - "requires": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "@types/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=", - "dev": true - }, - "@types/strip-json-comments": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", - "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", - "dev": true - }, - "@typescript-eslint/eslint-plugin": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.29.1.tgz", - "integrity": "sha512-AHqIU+SqZZgBEiWOrtN94ldR3ZUABV5dUG94j8Nms9rQnHFc8fvDOue/58K4CFz6r8OtDDc35Pw9NQPWo0Ayrw==", - "dev": true, - "requires": { - "@typescript-eslint/experimental-utils": "4.29.1", - "@typescript-eslint/scope-manager": "4.29.1", - "debug": "^4.3.1", - "functional-red-black-tree": "^1.0.1", - "regexpp": "^3.1.0", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "dependencies": { - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@typescript-eslint/experimental-utils": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.29.1.tgz", - "integrity": "sha512-kl6QG6qpzZthfd2bzPNSJB2YcZpNOrP6r9jueXupcZHnL74WiuSjaft7WSu17J9+ae9zTlk0KJMXPUj0daBxMw==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.29.1", - "@typescript-eslint/types": "4.29.1", - "@typescript-eslint/typescript-estree": "4.29.1", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - } - }, - "@typescript-eslint/parser": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.29.1.tgz", - "integrity": "sha512-3fL5iN20hzX3Q4OkG7QEPFjZV2qsVGiDhEwwh+EkmE/w7oteiOvUNzmpu5eSwGJX/anCryONltJ3WDmAzAoCMg==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "4.29.1", - "@typescript-eslint/types": "4.29.1", - "@typescript-eslint/typescript-estree": "4.29.1", - "debug": "^4.3.1" - }, - "dependencies": { - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@typescript-eslint/scope-manager": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.29.1.tgz", - "integrity": "sha512-Hzv/uZOa9zrD/W5mftZa54Jd5Fed3tL6b4HeaOpwVSabJK8CJ+2MkDasnX/XK4rqP5ZTWngK1ZDeCi6EnxPQ7A==", - "dev": true, - "requires": { - "@typescript-eslint/types": "4.29.1", - "@typescript-eslint/visitor-keys": "4.29.1" - } - }, - "@typescript-eslint/types": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.29.1.tgz", - "integrity": "sha512-Jj2yu78IRfw4nlaLtKjVaGaxh/6FhofmQ/j8v3NXmAiKafbIqtAPnKYrf0sbGjKdj0hS316J8WhnGnErbJ4RCA==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.29.1.tgz", - "integrity": "sha512-lIkkrR9E4lwZkzPiRDNq0xdC3f2iVCUjw/7WPJ4S2Sl6C3nRWkeE1YXCQ0+KsiaQRbpY16jNaokdWnm9aUIsfw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "4.29.1", - "@typescript-eslint/visitor-keys": "4.29.1", - "debug": "^4.3.1", - "globby": "^11.0.3", - "is-glob": "^4.0.1", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "dependencies": { - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@typescript-eslint/visitor-keys": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.29.1.tgz", - "integrity": "sha512-zLqtjMoXvgdZY/PG6gqA73V8BjqPs4af1v2kiiETBObp+uC6gRYnJLmJHxC0QyUrrHDLJPIWNYxoBV3wbcRlag==", - "dev": true, - "requires": { - "@typescript-eslint/types": "4.29.1", - "eslint-visitor-keys": "^2.0.0" - } - }, - "@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true - }, - "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==", - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, - "acorn": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.4.1.tgz", - "integrity": "sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA==" - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "acorn-walk": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.1.1.tgz", - "integrity": "sha512-FbJdceMlPHEAWJOILDk1fXD8lnTlEIWFkqtfk+MvmL5q/qlHfN7GEHcsFZWt/Tea9jRNPWUZG4G976nqAAmU9w==" - }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "dependencies": { - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - } - } - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "array-includes": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz", - "integrity": "sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2", - "get-intrinsic": "^1.1.1", - "is-string": "^1.0.5" - } - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" - }, - "array.prototype.flat": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz", - "integrity": "sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1" - } - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, - "axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "requires": { - "follow-redirects": "^1.14.0" - } - }, - "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 - }, - "base64url": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", - "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==" - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "dependencies": { - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - } - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "requires": { - "fill-range": "^7.0.1" - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" - }, - "cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==" - }, - "cacheable-request": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", - "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - } - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true - }, - "chai": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", - "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", - "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "dependencies": { - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - } - } - }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==" - }, - "clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", - "requires": { - "mimic-response": "^1.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "connect-redis": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-6.0.0.tgz", - "integrity": "sha512-6eGEAAPHYvcfbRNCMmPzBIjrqRWLw7at9lCUH4G6NQ8gwWDJelaUmFNOqPIhehbw941euVmIuqWsaWiKXfb+5g==" - }, - "content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "requires": { - "safe-buffer": "5.2.1" - } - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" - }, - "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "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==", - "requires": { - "mimic-response": "^3.1.0" - }, - "dependencies": { - "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==" - } - } - }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "requires": { - "type-detect": "^4.0.0" - } - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "denque": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", - "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==" - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "requires": { - "path-type": "^4.0.0" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "dotenv": { - "version": "16.4.5", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", - "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==" - }, - "dynamic-dedupe": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", - "integrity": "sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE=", - "dev": true, - "requires": { - "xtend": "^4.0.0" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "requires": { - "once": "^1.4.0" - } - }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.18.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.5.tgz", - "integrity": "sha512-DDggyJLoS91CkJjgauM5c0yZMjiD1uK3KcaCeAmffGwZ+ODWzOkPN4QwRbsK5DOFf06fywmyLci3ZD8jLGhVYA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.2", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.3", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.3", - "is-string": "^1.0.6", - "object-inspect": "^1.11.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "dev": true, - "requires": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "eslint-config-standard": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-16.0.3.tgz", - "integrity": "sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg==", - "dev": true, - "requires": {} - }, - "eslint-import-resolver-node": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.5.tgz", - "integrity": "sha512-XMoPKjSpXbkeJ7ZZ9icLnJMTY5Mc1kZbCakHquaFsXPpyWOwK0TK6CODO+0ca54UoM9LKOxyUNnoVZRl8TeaAg==", - "dev": true, - "requires": { - "debug": "^3.2.7", - "resolve": "^1.20.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - } - } - }, - "eslint-module-utils": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.2.tgz", - "integrity": "sha512-QG8pcgThYOuqxupd06oYTZoNOGaUdTY1PqK+oS6ElF6vs4pBdk/aYxFVQQXzcrAqp9m7cl7lb2ubazX+g16k2Q==", - "dev": true, - "requires": { - "debug": "^3.2.7", - "pkg-dir": "^2.0.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - } - } - }, - "eslint-plugin-es": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", - "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", - "dev": true, - "requires": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - }, - "dependencies": { - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "eslint-plugin-import": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.24.0.tgz", - "integrity": "sha512-Kc6xqT9hiYi2cgybOc0I2vC9OgAYga5o/rAFinam/yF/t5uBqxQbauNPMC6fgb640T/89P0gFoO27FOilJ/Cqg==", - "dev": true, - "requires": { - "array-includes": "^3.1.3", - "array.prototype.flat": "^1.2.4", - "debug": "^2.6.9", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.5", - "eslint-module-utils": "^2.6.2", - "find-up": "^2.0.0", - "has": "^1.0.3", - "is-core-module": "^2.4.0", - "minimatch": "^3.0.4", - "object.values": "^1.1.3", - "pkg-up": "^2.0.0", - "read-pkg-up": "^3.0.0", - "resolve": "^1.20.0", - "tsconfig-paths": "^3.9.0" - }, - "dependencies": { - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - } - } - }, - "eslint-plugin-node": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", - "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", - "dev": true, - "requires": { - "eslint-plugin-es": "^3.0.0", - "eslint-utils": "^2.0.0", - "ignore": "^5.1.1", - "minimatch": "^3.0.4", - "resolve": "^1.10.1", - "semver": "^6.1.0" - }, - "dependencies": { - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "eslint-plugin-promise": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.1.0.tgz", - "integrity": "sha512-NGmI6BH5L12pl7ScQHbg7tvtk4wPxxj8yPHH47NvSmMtFneC077PSeY3huFj06ZWZvtbfxSPt3RuOQD5XcR4ng==", - "dev": true, - "requires": {} - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - } - }, - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - }, - "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" - }, - "express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "requires": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" - } - } - }, - "express-graphql": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/express-graphql/-/express-graphql-0.12.0.tgz", - "integrity": "sha512-DwYaJQy0amdy3pgNtiTDuGGM2BLdj+YO2SgbKoLliCfuHv3VVTt7vNG/ZqK2hRYjtYHE2t2KB705EU94mE64zg==", - "requires": { - "accepts": "^1.3.7", - "content-type": "^1.0.4", - "http-errors": "1.8.0", - "raw-body": "^2.4.1" - }, - "dependencies": { - "http-errors": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz", - "integrity": "sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - } - } - }, - "express-http-proxy": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/express-http-proxy/-/express-http-proxy-2.0.0.tgz", - "integrity": "sha512-TXxcPFTWVUMSEmyM6iX2sT/JtmqhqngTq29P+eXTVFdtxZrTmM8THUYK59rUXiln0FfPGvxEpGRnVrgvHksXDw==", - "requires": { - "debug": "^3.0.1", - "es6-promise": "^4.1.1", - "raw-body": "^2.3.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - } - } - }, - "express-openid-connect": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/express-openid-connect/-/express-openid-connect-2.17.1.tgz", - "integrity": "sha512-5pVK6PNV09x6UN29R9Mer0XF3hwQq2HxiFsjZvLuIQ9ezeTUGbqrefzBOpzciz1S/1WWVaVPDIcj4EBpD8WB3Q==", - "requires": { - "base64url": "^3.0.1", - "clone": "^2.1.2", - "cookie": "^0.5.0", - "debug": "^4.3.4", - "futoin-hkdf": "^1.5.1", - "http-errors": "^1.8.1", - "joi": "^17.7.0", - "jose": "^2.0.6", - "on-headers": "^1.0.2", - "openid-client": "^4.9.1", - "url-join": "^4.0.1" - }, - "dependencies": { - "debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "requires": { - "ms": "2.1.2" - } - }, - "http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" - } - } - }, - "express-session": { - "version": "1.17.2", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.2.tgz", - "integrity": "sha512-mPcYcLA0lvh7D4Oqr5aNJFMtBMKPLl++OKKxkHzZ0U0oDq1rpKBnkR5f5vCHR26VeArlTOEF9td4x5IjICksRQ==", - "requires": { - "cookie": "0.4.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-headers": "~1.0.2", - "parseurl": "~1.3.3", - "safe-buffer": "5.2.1", - "uid-safe": "~2.1.5" - }, - "dependencies": { - "cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - } - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fastq": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.1.tgz", - "integrity": "sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw==", - "requires": { - "reusify": "^1.0.4" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "dependencies": { - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" - } - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", - "dev": true - }, - "follow-redirects": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", - "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==" - }, - "forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "futoin-hkdf": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/futoin-hkdf/-/futoin-hkdf-1.5.3.tgz", - "integrity": "sha512-SewY5KdMpaoCeh7jachEWFsh1nNlaDjNHZXWqL5IGwtpEYHTgkr2+AMCgNwKWkcc0wpSYrZfR7he4WdmHFtDxQ==" - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "requires": { - "pump": "^3.0.0" - } - }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "requires": { - "is-glob": "^4.0.1" - } - }, - "globals": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.10.0.tgz", - "integrity": "sha512-piHC3blgLGFjvOuMmWZX60f+na1lXFDhQXBf1UYp2fXPXqvEUbOhNwi6BsQ0bQishwedgnjkwv1d9zKf+MWw3g==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "globby": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", - "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - } - }, - "got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "requires": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - } - }, - "graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", - "dev": true - }, - "graphql": { - "version": "15.5.1", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.5.1.tgz", - "integrity": "sha512-FeTRX67T3LoE3LWAxxOlW2K3Bz+rMYAC18rRguK4wgXaTZMiJwSUwDmPFo3UadAKbzirKIg5Qy+sNJXbpPRnQw==" - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" - }, - "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true - }, - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" - }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "dependencies": { - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" - } - } - }, - "http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "requires": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==" - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - } - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-bigint": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.3.tgz", - "integrity": "sha512-ZU538ajmYJmzysE5yU4Y7uIrPQ2j704u+hXFiIPQExpqzzUbpe5jCPdTfmz7jXRxZdvjY3KZ3ZNenoXQovX+Dg==", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", - "dev": true - }, - "is-core-module": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.5.0.tgz", - "integrity": "sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" - }, - "is-number-object": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", - "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true - }, - "is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "joi": { - "version": "17.13.1", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.1.tgz", - "integrity": "sha512-vaBlIKCyo4FCUtCm7Eu4QZd/q02bWcxfUO6YSXAZOWF6gzcLBeba8kwotUdYJjDLW8Cz8RywsSOqiNJZW0mNvg==", - "requires": { - "@hapi/hoek": "^9.3.0", - "@hapi/topo": "^5.1.0", - "@sideway/address": "^4.1.5", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } - }, - "jose": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.7.tgz", - "integrity": "sha512-5hFWIigKqC+e/lRyQhfnirrAqUdIPMB7SJRqflJaO29dW7q5DFvH1XCSTmv6PQ6pb++0k6MJlLRoS0Wv4s38Wg==", - "requires": { - "@panva/asn1.js": "^1.0.0" - } - }, - "js-base64": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.6.1.tgz", - "integrity": "sha512-Frdq2+tRRGLQUIQOgsIGSCd1VePCS2fsddTG5dTCqR0JHgltXWfsxnY0gIXPoMeRmdom6Oyq+UMOFg5suduOjQ==" - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "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, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true - }, - "keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "requires": { - "json-buffer": "3.0.1" - } - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, - "log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" - }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "mocha": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", - "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", - "dev": true, - "requires": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.3", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "4.2.1", - "ms": "2.1.3", - "nanoid": "3.3.1", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "which": "2.0.2", - "workerpool": "6.2.0", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "minimatch": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", - "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true - } - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" - }, - "object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==" - }, - "object-inspect": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==" - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - }, - "object.values": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.4.tgz", - "integrity": "sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.2" - } - }, - "oidc-token-hash": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", - "integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==" - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } - }, - "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==" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "openid-client": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-4.9.1.tgz", - "integrity": "sha512-DYUF07AHjI3QDKqKbn2F7RqozT4hyi4JvmpodLrq0HHoNP7t/AjeG/uqiBK1/N2PZSAQEThVjDLHSmJN4iqu/w==", - "requires": { - "aggregate-error": "^3.1.0", - "got": "^11.8.0", - "jose": "^2.0.5", - "lru-cache": "^6.0.0", - "make-error": "^1.3.6", - "object-hash": "^2.0.1", - "oidc-token-hash": "^5.0.1" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==" - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" - }, - "pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true - }, - "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==" - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } - }, - "pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", - "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prisma": { - "version": "5.9.0-dev.58", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.9.0-dev.58.tgz", - "integrity": "sha512-dAz5V9Wg0fXKKvfXreDc7ckO/sX1ZJXa3aCkGSjgoD799AJz3e6DSR4QmsTcVaHzCp0h5Ocs3Fu/BFyHjJxgCQ==", - "devOptional": true, - "requires": { - "@prisma/engines": "5.9.0-dev.58" - } - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "requires": { - "side-channel": "^1.0.4" - } - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" - }, - "quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" - }, - "random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - }, - "dependencies": { - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "^3.0.0" - } - } - } - }, - "read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "redis": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/redis/-/redis-3.1.2.tgz", - "integrity": "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==", - "requires": { - "denque": "^1.5.0", - "redis-commands": "^1.7.0", - "redis-errors": "^1.2.0", - "redis-parser": "^3.0.0" - } - }, - "redis-commands": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz", - "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==" - }, - "redis-errors": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", - "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=" - }, - "redis-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", - "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=", - "requires": { - "redis-errors": "^1.0.0" - } - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "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 - }, - "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "dev": true, - "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - } - }, - "resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "requires": { - "lowercase-keys": "^2.0.0" - } - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "dependencies": { - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" - } - } - }, - "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - } - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" - }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz", - "integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==", - "dev": true - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "table": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", - "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", - "dev": true, - "requires": { - "ajv": "^8.0.1", - "lodash.clonedeep": "^4.5.0", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.6.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz", - "integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "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 - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "requires": { - "is-number": "^7.0.0" - } - }, - "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" - }, - "tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true - }, - "ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "requires": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - } - }, - "ts-node-dev": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz", - "integrity": "sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==", - "dev": true, - "requires": { - "chokidar": "^3.5.1", - "dynamic-dedupe": "^0.3.0", - "minimist": "^1.2.6", - "mkdirp": "^1.0.4", - "resolve": "^1.0.0", - "rimraf": "^2.6.1", - "source-map-support": "^0.5.12", - "tree-kill": "^1.2.2", - "ts-node": "^10.4.0", - "tsconfig": "^7.0.0" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, - "tsconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", - "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", - "dev": true, - "requires": { - "@types/strip-bom": "^3.0.0", - "@types/strip-json-comments": "0.0.30", - "strip-bom": "^3.0.0", - "strip-json-comments": "^2.0.0" - }, - "dependencies": { - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - } - } - }, - "tsconfig-paths": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.10.1.tgz", - "integrity": "sha512-rETidPDgCpltxF7MjBZlAFPUHv5aHH2MymyPvh+vEyWAED4Eb/WeMbsnD/JDr4OKPOA1TssDHgIcpTN5Kh0p6Q==", - "dev": true, - "requires": { - "json5": "^2.2.0", - "minimist": "^1.2.0", - "strip-bom": "^3.0.0" - } - }, - "tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==" - }, - "uid-safe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", - "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", - "requires": { - "random-bytes": "~1.0.0" - } - }, - "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - } - }, - "unixify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unixify/-/unixify-1.0.0.tgz", - "integrity": "sha1-OmQcjC/7zk2mg6XHDwOkYpQMIJA=", - "requires": { - "normalize-path": "^2.1.1" - } - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "url-join": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", - "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "value-or-promise": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.10.tgz", - "integrity": "sha512-1OwTzvcfXkAfabk60UVr5NdjtjJ0Fg0T5+B1bhxtrOEwSH2fe8y4DnLgoksfCyd8yZCOQQHB0qLMQnwgCjbXLQ==" - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "workerpool": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", - "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", - "dev": true - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true - }, - "yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "requires": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "dependencies": { - "decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true - } - } - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } - } -} diff --git a/backend/package.json b/backend/package.json index 0f8e108..4a54b39 100644 --- a/backend/package.json +++ b/backend/package.json @@ -2,58 +2,47 @@ "name": "backend", "version": "1.0.0", "description": "", + "license": "ISC", + "author": "", + "files": [ + "build", + "prisma", + "prisma.config.ts", + "startup.sh" + ], "main": "index.js", "scripts": { - "start": "ts-node src/index.ts", - "dev": "ts-node-dev -r dotenv/config --log-error --unhandled-rejections=none src/index.ts", + "start": "tsx src/index.ts", + "dev": "tsx watch --env-file=.env src/index.ts", "migrate": "prisma db push", - "test": "mocha -r ts-node/register ./src/tests/*.test.ts", - "build": "tsc" + "test": "vp test run", + "typecheck": "tsc --noEmit", + "build": "tsc -p tsconfig.build.json" }, - "author": "", - "license": "ISC", "dependencies": { - "@graphql-tools/load-files": "^6.3.2", - "@graphql-tools/merge": "^7.0.0", - "@graphql-tools/schema": "^8.0.3", - "@prisma/client": "^5.9.0-dev.58", - "axios": "^0.21.1", - "connect-redis": "^6.0.0", - "dotenv": "^16.4.5", - "express": "^4.17.1", - "express-graphql": "^0.12.0", - "express-http-proxy": "^2.0.0", - "express-openid-connect": "^2.17.1", - "express-session": "^1.17.2", - "graphql": "^15.5.1", - "js-base64": "^3.6.1", - "qs": "^6.10.1", - "redis": "^3.1.2", - "ts-node": "^10.9.1" + "@apollo/server": "^5.5.1", + "@as-integrations/express5": "^1.1.2", + "@graphql-tools/load-files": "^7.0.1", + "@graphql-tools/merge": "^9.2.3", + "@graphql-tools/schema": "^10.1.0", + "@prisma/adapter-pg": "^7.10.0", + "@prisma/client": "^7.10.0", + "axios": "^1.20.0", + "dotenv": "^17.4.2", + "express": "^5.2.1", + "express-http-proxy": "^2.1.2", + "express-openid-connect": "^3.4.0", + "graphql": "^16.12.0", + "pg": "^8.16.3", + "prisma": "^7.10.0", + "redis": "^6.2.1", + "tsx": "^4.21.0" }, "devDependencies": { - "@testdeck/mocha": "^0.1.2", - "@types/axios": "^0.14.0", - "@types/chai": "^4.2.21", - "@types/connect-redis": "^0.0.17", - "@types/express": "^4.17.13", - "@types/express-http-proxy": "^1.6.6", - "@types/express-session": "^1.17.4", - "@types/mocha": "^9.0.0", - "@types/node": "^16.4.13", - "@types/passport": "^1.0.7", - "@types/redis": "^2.8.31", - "@typescript-eslint/eslint-plugin": "^4.29.1", - "@typescript-eslint/parser": "^4.29.1", - "chai": "^4.3.4", - "eslint": "^7.32.0", - "eslint-config-standard": "^16.0.3", - "eslint-plugin-import": "^2.24.0", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^5.1.0", - "mocha": "^9.0.3", - "prisma": "^5.9.0-dev.58", - "ts-node-dev": "^2.0.0", - "typescript": "^4.9.5" + "@types/express": "^5.0.6", + "@types/express-http-proxy": "^1.6.7", + "@types/node": "^24.10.1", + "@types/pg": "^8.15.6", + "typescript": "^7.0.2" } } diff --git a/backend/prisma.config.ts b/backend/prisma.config.ts new file mode 100644 index 0000000..9e31194 --- /dev/null +++ b/backend/prisma.config.ts @@ -0,0 +1,9 @@ +import "dotenv/config"; +import { defineConfig } from "prisma/config"; + +export default defineConfig({ + schema: "prisma/schema.prisma", + datasource: { + url: process.env.DATABASE_URL, + }, +}); diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 88dd5d0..a39929a 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -4,7 +4,6 @@ generator client { datasource db { provider = "postgresql" - url = env("DATABASE_URL") } model event { diff --git a/backend/src/auth/session-store.ts b/backend/src/auth/session-store.ts new file mode 100644 index 0000000..8f59d27 --- /dev/null +++ b/backend/src/auth/session-store.ts @@ -0,0 +1,47 @@ +import type { ConfigParams } from "express-openid-connect"; + +type SessionConfig = Exclude; +type OidcStore = NonNullable; +type Payload = Parameters[1]; + +export interface SessionRedis { + get(key: string): Promise; + set(key: string, value: string, options: { EX: number }): Promise; + del(key: string): Promise; +} + +// OIDC stores have a callback contract, unlike the current connect-redis API. +// Expire the Redis data with the OIDC session itself. +export function createSessionStore(client: SessionRedis): OidcStore { + const key = (sid: string) => `bookit:oidc:${sid}`; + return { + get(sid, callback) { + void client + .get(key(sid)) + .then((value) => { + if (!value) return null; + const payload = JSON.parse(value) as Payload; + return payload.header.exp > Date.now() / 1000 ? payload : null; + }) + .then( + (payload) => callback(null, payload), + (error: unknown) => callback(error), + ); + }, + set(sid, payload, callback) { + const ttl = Math.ceil(payload.header.exp - Date.now() / 1000); + const operation = + ttl > 0 ? client.set(key(sid), JSON.stringify(payload), { EX: ttl }) : client.del(key(sid)); + void operation.then( + () => callback?.(), + (error: unknown) => callback?.(error), + ); + }, + destroy(sid, callback) { + void client.del(key(sid)).then( + () => callback?.(), + (error: unknown) => callback?.(error), + ); + }, + }; +} diff --git a/backend/src/auth/user.ts b/backend/src/auth/user.ts new file mode 100644 index 0000000..ca0dc8d --- /dev/null +++ b/backend/src/auth/user.ts @@ -0,0 +1,43 @@ +import type { Request } from "express"; +import { GraphQLError } from "graphql"; +import type { User } from "../models/user"; + +declare global { + namespace Express { + interface Request { + appSession?: { + groups?: unknown; + is_admin?: unknown; + }; + } + } +} + +export function authenticatedUser(req: Request): User { + const claims = req.oidc.user; + if ( + !req.oidc.isAuthenticated() || + typeof claims?.sub !== "string" || + typeof claims.cid !== "string" + ) { + throw new GraphQLError("Authentication required", { extensions: { code: "UNAUTHENTICATED" } }); + } + const claim = (name: string): string => (typeof claims[name] === "string" ? claims[name] : ""); + const groups: unknown = req.appSession?.groups; + return { + sub: claims.sub, + cid: claims.cid, + sid: claim("sid"), + given_name: claim("given_name"), + locale: claim("locale"), + picture: claim("picture"), + name: claim("name"), + nickname: claim("nickname"), + family_name: claim("family_name"), + jti: claim("jti"), + groups: Array.isArray(groups) + ? groups.filter((group): group is string => typeof group === "string") + : [], + is_admin: req.appSession?.is_admin === true, + }; +} diff --git a/backend/src/generated/schema.ts b/backend/src/generated/schema.ts new file mode 100644 index 0000000..f126ad7 --- /dev/null +++ b/backend/src/generated/schema.ts @@ -0,0 +1,369 @@ +import type { GraphQLResolveInfo } from 'graphql'; +import type { Context } from '../utils/commonTypes'; +export type Maybe = T | null; +export type InputMaybe = Maybe; +export type RequireFields = Omit & { [P in K]-?: NonNullable }; +/** All built-in and custom scalars, mapped to their actual values */ +export type Scalars = { + ID: { input: string; output: string; } + String: { input: string; output: string; } + Boolean: { input: boolean; output: boolean; } + Int: { input: number; output: number; } + Float: { input: number; output: number; } +}; + +export type Error = { + __typename?: 'Error'; + sv: Scalars['String']['output']; + en: Scalars['String']['output']; +}; + +export type Event = { + __typename?: 'Event'; + id?: Maybe; + start: Scalars['String']['output']; + end: Scalars['String']['output']; + description?: Maybe; + title: Scalars['String']['output']; + created_at?: Maybe; + updated_at?: Maybe; + room: Array; + phone?: Maybe; + booked_as: Scalars['String']['output']; + booked_by: Scalars['String']['output']; +}; + +export type Query = { + __typename?: 'Query'; + events?: Maybe>>; + event?: Maybe; + eventsFT?: Maybe>>; + illegalSlots?: Maybe>; + rules?: Maybe>>; + rule?: Maybe; + user?: Maybe; +}; + + +export type QueryEventArgs = { + id: Scalars['String']['input']; +}; + + +export type QueryEventsFtArgs = { + from: Scalars['String']['input']; + to: Scalars['String']['input']; +}; + + +export type QueryIllegalSlotsArgs = { + from: Scalars['String']['input']; + to: Scalars['String']['input']; +}; + + +export type QueryRuleArgs = { + id: Scalars['String']['input']; +}; + +export type InputEvent = { + id?: InputMaybe; + start: Scalars['String']['input']; + end: Scalars['String']['input']; + description?: InputMaybe; + title: Scalars['String']['input']; + created_at?: InputMaybe; + updated_at?: InputMaybe; + room: Array; + phone?: InputMaybe; + booked_as: Scalars['String']['input']; + booking_terms: Scalars['Boolean']['input']; +}; + +export type Mutation = { + __typename?: 'Mutation'; + createEvent?: Maybe; + editEvent?: Maybe; + deleteEvent?: Maybe; + createRule?: Maybe; + deleteRule?: Maybe; +}; + + +export type MutationCreateEventArgs = { + event: InputEvent; +}; + + +export type MutationEditEventArgs = { + event: InputEvent; +}; + + +export type MutationDeleteEventArgs = { + id: Scalars['String']['input']; +}; + + +export type MutationCreateRuleArgs = { + rule: InputRule; +}; + + +export type MutationDeleteRuleArgs = { + id: Scalars['String']['input']; +}; + +export type IllegalSlot = { + __typename?: 'IllegalSlot'; + room: Array; + start: Scalars['String']['output']; + end: Scalars['String']['output']; + description?: Maybe; + title: Scalars['String']['output']; +}; + +export type Room = + | 'BIG_HUB' + | 'GROUP_ROOM' + | 'CTC' + | 'THE_CLOUD'; + +export type Rule = { + __typename?: 'Rule'; + id?: Maybe; + day_mask?: Maybe; + start_date?: Maybe; + end_date?: Maybe; + start_time?: Maybe; + end_time?: Maybe; + description?: Maybe; + allow?: Maybe; + priority?: Maybe; + title?: Maybe; + created_at?: Maybe; + updated_at?: Maybe; + room?: Maybe>; +}; + +export type InputRule = { + id?: InputMaybe; + day_mask?: InputMaybe; + start_date: Scalars['String']['input']; + end_date: Scalars['String']['input']; + start_time: Scalars['String']['input']; + end_time: Scalars['String']['input']; + description: Scalars['String']['input']; + allow?: InputMaybe; + priority?: InputMaybe; + title: Scalars['String']['input']; + created_at?: InputMaybe; + updated_at?: InputMaybe; + room: Array; +}; + +export type User = { + __typename?: 'User'; + sid?: Maybe; + sub?: Maybe; + given_name?: Maybe; + locale?: Maybe; + picture?: Maybe; + name?: Maybe; + nickname?: Maybe; + family_name?: Maybe; + jti?: Maybe; + cid?: Maybe; + groups?: Maybe>>; + is_admin?: Maybe; +}; + + + +export type ResolverTypeWrapper = Promise | T; + + +export type ResolverWithResolve = { + resolve: ResolverFn; +}; +export type Resolver, TContext = Record, TArgs = Record> = ResolverFn | ResolverWithResolve; + +export type ResolverFn = ( + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo +) => Promise | TResult; + +export type SubscriptionSubscribeFn = ( + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo +) => AsyncIterable | Promise>; + +export type SubscriptionResolveFn = ( + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo +) => TResult | Promise; + +export interface SubscriptionSubscriberObject { + subscribe: SubscriptionSubscribeFn<{ [key in TKey]: TResult }, TParent, TContext, TArgs>; + resolve?: SubscriptionResolveFn; +} + +export interface SubscriptionResolverObject { + subscribe: SubscriptionSubscribeFn; + resolve: SubscriptionResolveFn; +} + +export type SubscriptionObject = + | SubscriptionSubscriberObject + | SubscriptionResolverObject; + +export type SubscriptionResolver, TContext = Record, TArgs = Record> = + | ((...args: any[]) => SubscriptionObject) + | SubscriptionObject; + +export type TypeResolveFn, TContext = Record> = ( + parent: TParent, + context: TContext, + info: GraphQLResolveInfo +) => Maybe | Promise>; + +export type IsTypeOfResolverFn, TContext = Record> = (obj: T, context: TContext, info: GraphQLResolveInfo) => boolean | Promise; + +export type NextResolverFn = () => Promise; + +export type DirectiveResolverFn, TParent = Record, TContext = Record, TArgs = Record> = ( + next: NextResolverFn, + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo +) => TResult | Promise; + + + + + +/** Mapping between all available schema types and the resolvers types */ +export type ResolversTypes = { + Error: ResolverTypeWrapper; + String: ResolverTypeWrapper; + Event: ResolverTypeWrapper; + Query: ResolverTypeWrapper>; + InputEvent: InputEvent; + Boolean: ResolverTypeWrapper; + Mutation: ResolverTypeWrapper>; + IllegalSlot: ResolverTypeWrapper; + Room: Room; + Rule: ResolverTypeWrapper; + Int: ResolverTypeWrapper; + InputRule: InputRule; + User: ResolverTypeWrapper; +}; + +/** Mapping between all available schema types and the resolvers parents */ +export type ResolversParentTypes = { + Error: Error; + String: Scalars['String']['output']; + Event: Event; + Query: Record; + InputEvent: InputEvent; + Boolean: Scalars['Boolean']['output']; + Mutation: Record; + IllegalSlot: IllegalSlot; + Rule: Rule; + Int: Scalars['Int']['output']; + InputRule: InputRule; + User: User; +}; + +export type ErrorResolvers = { + sv?: Resolver; + en?: Resolver; +}; + +export type EventResolvers = { + id?: Resolver, ParentType, ContextType>; + start?: Resolver; + end?: Resolver; + description?: Resolver, ParentType, ContextType>; + title?: Resolver; + created_at?: Resolver, ParentType, ContextType>; + updated_at?: Resolver, ParentType, ContextType>; + room?: Resolver, ParentType, ContextType>; + phone?: Resolver, ParentType, ContextType>; + booked_as?: Resolver; + booked_by?: Resolver; +}; + +export type QueryResolvers = { + events?: Resolver>>, ParentType, ContextType>; + event?: Resolver, ParentType, ContextType, RequireFields>; + eventsFT?: Resolver>>, ParentType, ContextType, RequireFields>; + illegalSlots?: Resolver>, ParentType, ContextType, RequireFields>; + rules?: Resolver>>, ParentType, ContextType>; + rule?: Resolver, ParentType, ContextType, RequireFields>; + user?: Resolver, ParentType, ContextType>; +}; + +export type MutationResolvers = { + createEvent?: Resolver, ParentType, ContextType, RequireFields>; + editEvent?: Resolver, ParentType, ContextType, RequireFields>; + deleteEvent?: Resolver, ParentType, ContextType, RequireFields>; + createRule?: Resolver, ParentType, ContextType, RequireFields>; + deleteRule?: Resolver, ParentType, ContextType, RequireFields>; +}; + +export type IllegalSlotResolvers = { + room?: Resolver, ParentType, ContextType>; + start?: Resolver; + end?: Resolver; + description?: Resolver, ParentType, ContextType>; + title?: Resolver; +}; + +export type RuleResolvers = { + id?: Resolver, ParentType, ContextType>; + day_mask?: Resolver, ParentType, ContextType>; + start_date?: Resolver, ParentType, ContextType>; + end_date?: Resolver, ParentType, ContextType>; + start_time?: Resolver, ParentType, ContextType>; + end_time?: Resolver, ParentType, ContextType>; + description?: Resolver, ParentType, ContextType>; + allow?: Resolver, ParentType, ContextType>; + priority?: Resolver, ParentType, ContextType>; + title?: Resolver, ParentType, ContextType>; + created_at?: Resolver, ParentType, ContextType>; + updated_at?: Resolver, ParentType, ContextType>; + room?: Resolver>, ParentType, ContextType>; +}; + +export type UserResolvers = { + sid?: Resolver, ParentType, ContextType>; + sub?: Resolver, ParentType, ContextType>; + given_name?: Resolver, ParentType, ContextType>; + locale?: Resolver, ParentType, ContextType>; + picture?: Resolver, ParentType, ContextType>; + name?: Resolver, ParentType, ContextType>; + nickname?: Resolver, ParentType, ContextType>; + family_name?: Resolver, ParentType, ContextType>; + jti?: Resolver, ParentType, ContextType>; + cid?: Resolver, ParentType, ContextType>; + groups?: Resolver>>, ParentType, ContextType>; + is_admin?: Resolver, ParentType, ContextType>; +}; + +export type Resolvers = { + Error?: ErrorResolvers; + Event?: EventResolvers; + Query?: QueryResolvers; + Mutation?: MutationResolvers; + IllegalSlot?: IllegalSlotResolvers; + Rule?: RuleResolvers; + User?: UserResolvers; +}; diff --git a/backend/src/index.ts b/backend/src/index.ts index c52941e..f59dc1e 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,60 +1,116 @@ +import { createServer } from "node:http"; import express from "express"; -import session from "express-session"; -import { setupRoutes } from "./routes"; -import redis from "redis"; +import { createClient } from "redis"; import { PrismaClient } from "@prisma/client"; -const RedisStore = require("connect-redis")(session); +import { PrismaPg } from "@prisma/adapter-pg"; import { auth } from "express-openid-connect"; -import { UserInfo } from "./models/user"; +import { setupRoutes } from "./routes"; +import type { UserInfo } from "./models/user"; import { authRequest } from "./utils"; +import { createSessionStore } from "./auth/session-store"; -const app = express(); -const session_store = new RedisStore({ - client: redis.createClient({ - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), - password: process.env.REDIS_PASS, - db: 1, - }), -}) +interface GammaGroup { + superGroup?: { type: string; name: string }; +} -app.use( - session({ - secret: String(process.env.SESSION_SECRET), - store: session_store, - resave: false, - saveUninitialized: false, - }), -); +const requiredEnvironment = (name: string): string => { + const value = process.env[name]; + if (!value) throw new Error(`Missing required environment variable: ${name}`); + return value; +}; -app.use( - auth({ - idpLogout: true, - authRequired: true, - authorizationParams: { scope: "openid profile", response_type: "code" }, - clientAuthMethod: "client_secret_basic", - routes: { callback: "/api/callback" }, - session: { - store: session_store, +async function main() { + // Booking rule clock times belong to the division's local time zone. + process.env.TZ ??= "Europe/Stockholm"; + const app = express(); + app.disable("x-powered-by"); + if (process.env.TRUST_PROXY === "1") app.set("trust proxy", 1); + const httpServer = createServer(app); + const redis = createClient({ + // Redis 5 predates HELLO/RESP3. Keep the modern client on its supported + // RESP2 protocol until the separately planned Redis server upgrade. + RESP: 2, + maintNotifications: "disabled", + socket: { + host: process.env.REDIS_HOST || "localhost", + port: Number(process.env.REDIS_PORT || 6379), }, - afterCallback: async (req, res, session, decodedState) => { - const userInfo: UserInfo = await authRequest("/oauth2/userinfo", session.access_token) - const authorities: string[] = await authRequest(`/api/client/v1/authorities/for/${userInfo.sub}`) - const groups: any[] = await authRequest(`/api/client/v1/groups/for/${userInfo.sub}`) - return { - ...session, - is_admin: authorities.includes("admin"), - groups: groups - .map((group) => group?.superGroup) - .filter(group => group?.type !== "alumni") - .map(group => group?.name) - }; - } - }), -); + password: process.env.REDIS_PASS || undefined, + database: 1, + }); + redis.on("error", (error) => console.error("Redis connection error", error)); + const prisma = new PrismaClient({ + adapter: new PrismaPg({ connectionString: requiredEnvironment("DATABASE_URL") }), + }); -const prisma = new PrismaClient(); + app.get("/api/health", (_req, res) => res.json({ status: "ok" })); + app.use( + auth({ + secret: requiredEnvironment("SESSION_SECRET"), + idpLogout: true, + authRequired: true, + authorizationParams: { scope: "openid profile", response_type: "code" }, + clientAuthMethod: "client_secret_basic", + routes: { callback: "/api/callback", login: "/api/login", logout: "/api/logout" }, + session: { + store: createSessionStore(redis), + signSessionStoreCookie: true, + requireSignedSessionStoreCookie: true, + }, + afterCallback: async (_req, _res, session) => { + const userInfo = await authRequest("/oauth2/userinfo", session.access_token); + const [authorities, groups] = await Promise.all([ + authRequest( + `/api/client/v1/authorities/for/${encodeURIComponent(userInfo.sub)}`, + ), + authRequest( + `/api/client/v1/groups/for/${encodeURIComponent(userInfo.sub)}`, + ), + ]); + return { + ...session, + is_admin: authorities.includes("admin"), + groups: [ + ...new Set( + groups.flatMap(({ superGroup }) => + superGroup && superGroup.type.toLowerCase() !== "alumni" ? [superGroup.name] : [], + ), + ), + ], + }; + }, + }), + ); -setupRoutes(app, { prisma }); + await Promise.all([redis.connect(), prisma.$connect()]); + const apollo = await setupRoutes(app, { prisma }, httpServer); + app.use(((error, _req, res, _next) => { + console.error("Request failed", error instanceof Error ? error.message : "Unknown error"); + res.status(500).json({ error: "Request failed" }); + }) satisfies express.ErrorRequestHandler); + const port = Number(process.env.PORT || 8080); + await new Promise((resolve, reject) => { + httpServer.once("error", reject); + httpServer.listen(port, resolve); + }); + console.log(`BookIT listening on port ${port}`); + let stopping = false; + const stop = () => { + if (stopping) return; + stopping = true; + void apollo + .stop() + .then(() => Promise.all([redis.close(), prisma.$disconnect()])) + .catch((error) => { + console.error("Shutdown failed", error); + process.exitCode = 1; + }); + }; + process.once("SIGTERM", stop); + process.once("SIGINT", stop); +} -app.listen(Number(process.env.PORT) || 8080); +void main().catch((error) => { + console.error("BookIT startup failed", error instanceof Error ? error.message : "Unknown error"); + process.exit(1); +}); diff --git a/backend/src/models/event.ts b/backend/src/models/event.ts index 6993631..435ca1a 100644 --- a/backend/src/models/event.ts +++ b/backend/src/models/event.ts @@ -1,16 +1,5 @@ -import { room } from "@prisma/client"; +import type { InputEvent } from "../generated/schema"; -export interface Event { - id: string | null; - start: string; - end: string; - description: string | null; - title: string; - created_at: string; - updated_at: string; - room: room[]; - phone: string; +export interface Event extends InputEvent { booked_by: string; - booked_as: string; - booking_terms: boolean; } diff --git a/backend/src/models/rule.ts b/backend/src/models/rule.ts index 0042937..f4081e8 100644 --- a/backend/src/models/rule.ts +++ b/backend/src/models/rule.ts @@ -1,32 +1,2 @@ -import { room } from "@prisma/client"; - -export interface Rule { - id: string; - day_mask: number; - start_date: string; - end_date: string; - start_time: string; - end_time: string; - description: string; - allow: boolean; - priority: number; - title: string; - created_at: string; - updated_at: string; - room: room[]; -} -export interface dbRule { - id: string; - day_mask: number; - start_date: Date; - end_date: Date; - start_time: string; - end_time: string; - description: string; - allow: boolean; - priority: number; - title: string; - created_at: Date; - updated_at: Date; - room: room[]; -} +export type { InputRule as Rule } from "../generated/schema"; +export type { rule as dbRule } from "@prisma/client"; diff --git a/backend/src/models/user.ts b/backend/src/models/user.ts index e65c946..14e0896 100644 --- a/backend/src/models/user.ts +++ b/backend/src/models/user.ts @@ -14,4 +14,4 @@ export interface UserInfo { export interface User extends UserInfo { groups: string[]; is_admin: boolean; -} \ No newline at end of file +} diff --git a/backend/src/resolvers/event.resolver.ts b/backend/src/resolvers/event.resolver.ts index 72414c1..a5be5ce 100644 --- a/backend/src/resolvers/event.resolver.ts +++ b/backend/src/resolvers/event.resolver.ts @@ -1,49 +1,39 @@ import { Tools } from "../utils/commonTypes"; -import { Event } from "../models/event"; +import type { InputEvent } from "../generated/schema"; import { User } from "../models/user"; import { createEvent, editEvent, deleteEvent } from "../services/event.service"; +import { eventResult } from "./serialize"; export const getEventQResolvers = ({ prisma }: Tools) => ({ events: async () => { - return await prisma.event.findMany(); + return (await prisma.event.findMany()).map(eventResult); }, - eventsFT: async (_: any, ft: { from: string; to: string }) => { - return await prisma.event.findMany({ - where: { - end: { gte: new Date(ft.from) }, - start: { lte: new Date(ft.to) }, - }, - }); + eventsFT: async (_: unknown, ft: { from: string; to: string }) => { + return ( + await prisma.event.findMany({ + where: { + end: { gte: new Date(ft.from) }, + start: { lte: new Date(ft.to) }, + }, + }) + ).map(eventResult); }, - event: async (_: any, { id }: { id: string }) => { - return await prisma.event.findFirst({ + event: async (_: unknown, { id }: { id: string }) => { + const result = await prisma.event.findFirst({ where: { id: id }, }); + return result ? eventResult(result) : null; }, }); export const getEventMResolvers = ({ prisma }: Tools) => ({ - createEvent: async ( - _: any, - { event }: { event: Event }, - { user }: { user: User }, - ) => { - event.booked_by = user.cid; - return createEvent(prisma, event, user); + createEvent: async (_: unknown, { event }: { event: InputEvent }, { user }: { user: User }) => { + return createEvent(prisma, { ...event, booked_by: user.cid }, user); }, - editEvent: async ( - _: any, - { event }: { event: Event }, - { user }: { user: User }, - ) => { - event.booked_by = user.cid; - return editEvent(prisma, event, user); + editEvent: async (_: unknown, { event }: { event: InputEvent }, { user }: { user: User }) => { + return editEvent(prisma, { ...event, booked_by: user.cid }, user); }, - deleteEvent: async ( - _: any, - { id }: { id: string }, - { user }: { user: User }, - ) => { + deleteEvent: async (_: unknown, { id }: { id: string }, { user }: { user: User }) => { return deleteEvent(prisma, id, user); }, }); diff --git a/backend/src/resolvers/illegal_slots.resolver.ts b/backend/src/resolvers/illegal_slots.resolver.ts index 21bddf6..1453813 100644 --- a/backend/src/resolvers/illegal_slots.resolver.ts +++ b/backend/src/resolvers/illegal_slots.resolver.ts @@ -1,19 +1,25 @@ import { Tools } from "../utils/commonTypes"; -import { - mergeRules, - toExplicitRules, - getRulesBetween, -} from "../services/rule.service"; +import { room } from "@prisma/client"; +import { slotResult } from "./serialize"; +import { mergeRules, toExplicitRules, getRulesBetween } from "../services/rule.service"; export const getIllegalSlotsQResolvers = ({ prisma }: Tools) => ({ - illegalSlots: async (_: any, ft: { from: string; to: string }) => { + illegalSlots: async (_: unknown, ft: { from: string; to: string }) => { const from = new Date(ft.from); const to = new Date(ft.to); - const rules = await await getRulesBetween(prisma, from, to); + const rules = await getRulesBetween(prisma, from, to); - return mergeRules(toExplicitRules(rules, from, to)).filter( - rule => !rule.allow, + return Object.values(room).flatMap((selectedRoom) => + mergeRules( + toExplicitRules( + rules.filter((rule) => rule.room.includes(selectedRoom)), + from, + to, + ), + ) + .filter((rule) => !rule.allow) + .map((rule) => ({ ...slotResult(rule), room: [selectedRoom] })), ); }, }); diff --git a/backend/src/resolvers/index.ts b/backend/src/resolvers/index.ts index 5fc3da3..da8ecc2 100644 --- a/backend/src/resolvers/index.ts +++ b/backend/src/resolvers/index.ts @@ -3,9 +3,14 @@ import { getEventMResolvers, getEventQResolvers } from "./event.resolver"; import { getIllegalSlotsQResolvers } from "./illegal_slots.resolver"; import { getRuleMResolvers, getRuleQResolvers } from "./rule.resolver"; import { getUserQResolvers } from "./user.resolver"; +import type { Resolvers } from "../generated/schema"; export const getResolvers = (tools: Tools) => { return { + Event: { + phone: (event, _args, { user }) => + user.is_admin || user.cid === event.booked_by ? (event.phone ?? null) : null, + }, Query: { ...getUserQResolvers(), ...getEventQResolvers(tools), @@ -16,5 +21,5 @@ export const getResolvers = (tools: Tools) => { ...getEventMResolvers(tools), ...getRuleMResolvers(tools), }, - }; + } satisfies Resolvers; }; diff --git a/backend/src/resolvers/rule.resolver.ts b/backend/src/resolvers/rule.resolver.ts index 7d0e6b6..23284da 100644 --- a/backend/src/resolvers/rule.resolver.ts +++ b/backend/src/resolvers/rule.resolver.ts @@ -1,34 +1,27 @@ import { Rule, User } from "../models"; import { createRule, deleteRule } from "../services/rule.service"; -import { to } from "../utils"; import { Tools } from "../utils/commonTypes"; +import { ruleResult } from "./serialize"; export const getRuleQResolvers = ({ prisma }: Tools) => ({ - rules: () => { - return prisma.rule.findMany(); + rules: async () => { + return (await prisma.rule.findMany()).map(ruleResult); }, - rule: (_: any, { id }: { id: string }) => { - return prisma.rule.findUnique({ + rule: async (_: unknown, { id }: { id: string }) => { + const result = await prisma.rule.findUnique({ where: { id: id, }, }); + return result ? ruleResult(result) : null; }, }); export const getRuleMResolvers = ({ prisma }: Tools) => ({ - createRule: async ( - _: any, - { rule }: { rule: Rule }, - {user}: { user: User}, - ) => { + createRule: async (_: unknown, { rule }: { rule: Rule }, { user }: { user: User }) => { return createRule(prisma, rule, user); }, - deleteRule: async ( - _: any, - { id }: { id: string }, - {user}: { user: User}, - ) => { - return deleteRule(prisma, id, user); - }, + deleteRule: async (_: unknown, { id }: { id: string }, { user }: { user: User }) => { + return deleteRule(prisma, id, user); + }, }); diff --git a/backend/src/resolvers/serialize.ts b/backend/src/resolvers/serialize.ts new file mode 100644 index 0000000..ff72f44 --- /dev/null +++ b/backend/src/resolvers/serialize.ts @@ -0,0 +1,28 @@ +import type { event, rule } from "@prisma/client"; +import type { Event, Rule, Room } from "../generated/schema"; +import type { ExplicitRule } from "../services/rule.service"; + +export function eventResult(value: event): Event { + return { + ...value, + start: value.start.toISOString(), + end: value.end.toISOString(), + created_at: value.created_at.toISOString(), + updated_at: value.updated_at.toISOString(), + room: value.room as Room[], + }; +} + +export function ruleResult(value: rule): Rule { + return { + ...value, + start_date: value.start_date.toISOString(), + end_date: value.end_date.toISOString(), + created_at: value.created_at.toISOString(), + updated_at: value.updated_at.toISOString(), + }; +} + +export function slotResult(value: ExplicitRule) { + return { ...value, start: value.start.toISOString(), end: value.end.toISOString() }; +} diff --git a/backend/src/resolvers/user.resolver.ts b/backend/src/resolvers/user.resolver.ts index 45107a8..24255fb 100644 --- a/backend/src/resolvers/user.resolver.ts +++ b/backend/src/resolvers/user.resolver.ts @@ -1,7 +1,7 @@ import { User } from "../models/user"; export const getUserQResolvers = () => ({ - user: (_: any, __: any, context: { user: User }) => { + user: (_: unknown, __: unknown, context: { user: User }) => { return context.user; }, }); diff --git a/backend/src/routes/index.ts b/backend/src/routes/index.ts index 9e9df2c..7b07087 100644 --- a/backend/src/routes/index.ts +++ b/backend/src/routes/index.ts @@ -1,44 +1,32 @@ -import { join } from "path"; - -import { graphqlHTTP } from "express-graphql"; +import { join } from "node:path"; +import type { Server } from "node:http"; +import { ApolloServer } from "@apollo/server"; +import { ApolloServerPluginDrainHttpServer } from "@apollo/server/plugin/drainHttpServer"; +import { expressMiddleware } from "@as-integrations/express5"; import { loadFilesSync } from "@graphql-tools/load-files"; import { mergeTypeDefs } from "@graphql-tools/merge"; import { makeExecutableSchema } from "@graphql-tools/schema"; - -import { getResolvers } from "../resolvers"; -import proxy from "express-http-proxy"; - -// Import types import express from "express"; -import { Tools } from "../utils/commonTypes"; - -const setupGraphql = (app: express.Application, tools: Tools) => { - const graphiql = process.env.GRAPHIQL == "true"; - const typeDefs = mergeTypeDefs( - loadFilesSync(join(__dirname, "../schemas/v1/*.gql")), - ); +import proxy from "express-http-proxy"; +import { getResolvers } from "../resolvers"; +import type { Context, Tools } from "../utils/commonTypes"; +import { authenticatedUser } from "../auth/user"; - const router = express.Router(); - router.use( - "/v1", - graphqlHTTP(async (req: any) => ({ - schema: makeExecutableSchema({ - typeDefs: typeDefs, - resolvers: getResolvers(tools), - }), - graphiql: graphiql, - context: {user: { - ...req.oidc.user, - groups: req.appSession.groups, - is_admin: req.appSession.is_admin - }}, - })), +export const setupRoutes = async (app: express.Application, tools: Tools, httpServer: Server) => { + const typeDefs = mergeTypeDefs(loadFilesSync(join(__dirname, "../schemas/v1/*.gql"))); + const server = new ApolloServer({ + schema: makeExecutableSchema({ typeDefs, resolvers: getResolvers(tools) }), + plugins: [ApolloServerPluginDrainHttpServer({ httpServer })], + includeStacktraceInErrorResponses: false, + }); + await server.start(); + app.use( + "/api/graphql/v1", + express.json({ limit: "100kb" }), + expressMiddleware(server, { + context: async ({ req }: { req: express.Request }) => ({ user: authenticatedUser(req) }), + }), ); - app.use("/api/graphql", router); -}; - -export const setupRoutes = (app: express.Application, tools: Tools) => { - setupGraphql(app, tools); - const frontend_url = process.env.FRONTEND_URL || "http://localhost:3001"; - app.use("/", proxy(frontend_url)); + app.use("/", proxy(process.env.FRONTEND_URL || "http://localhost:3001")); + return server; }; diff --git a/backend/src/schemas/v1/event.gql b/backend/src/schemas/v1/event.gql index 059f44a..259d304 100644 --- a/backend/src/schemas/v1/event.gql +++ b/backend/src/schemas/v1/event.gql @@ -6,15 +6,15 @@ type Event { title: String! created_at: String updated_at: String - room: [Room]! - phone: String! + room: [Room!]! + phone: String booked_as: String! booked_by: String! } type Query { events: [Event] - event(id: String): Event + event(id: String!): Event eventsFT(from: String!, to: String!): [Event] } @@ -26,14 +26,14 @@ input InputEvent { title: String! created_at: String updated_at: String - room: [Room]! - phone: String! + room: [Room!]! + phone: String booked_as: String! booking_terms: Boolean! } type Mutation { - createEvent(event: InputEvent): Error - editEvent(event: InputEvent): Error - deleteEvent(id: String): Error + createEvent(event: InputEvent!): Error + editEvent(event: InputEvent!): Error + deleteEvent(id: String!): Error } diff --git a/backend/src/schemas/v1/illegal_slots.gql b/backend/src/schemas/v1/illegal_slots.gql index 5d20596..18e32b8 100644 --- a/backend/src/schemas/v1/illegal_slots.gql +++ b/backend/src/schemas/v1/illegal_slots.gql @@ -1,4 +1,5 @@ type IllegalSlot { + room: [Room!]! start: String! end: String! description: String diff --git a/backend/src/schemas/v1/rule.gql b/backend/src/schemas/v1/rule.gql index 4df34bb..810716c 100644 --- a/backend/src/schemas/v1/rule.gql +++ b/backend/src/schemas/v1/rule.gql @@ -11,31 +11,31 @@ type Rule { title: String created_at: String updated_at: String - room: [Room] + room: [Room!] } input InputRule { id: String - day_mask: Int + day_mask: Int = 0 start_date: String! end_date: String! start_time: String! end_time: String! description: String! - allow: Boolean - priority: Int + allow: Boolean = true + priority: Int = 10 title: String! created_at: String updated_at: String - room: [Room]! + room: [Room!]! } type Query { rules: [Rule] - rule(id: String): Rule + rule(id: String!): Rule } type Mutation { - createRule(rule: InputRule): Error - deleteRule(id: String): Boolean + createRule(rule: InputRule!): Error + deleteRule(id: String!): Error } diff --git a/backend/src/services/event.service.ts b/backend/src/services/event.service.ts index 1dc3227..faf8c33 100644 --- a/backend/src/services/event.service.ts +++ b/backend/src/services/event.service.ts @@ -1,4 +1,4 @@ -import { PrismaClient, event, room } from "@prisma/client"; +import { Prisma, PrismaClient, event, room } from "@prisma/client"; import { Error, User } from "../models"; import { Event } from "../models/event"; import { checkRules } from "./rule.service"; @@ -51,19 +51,18 @@ const roomSpecified = (event: Event) => { * The new event may not overlap with any existing events */ const overlappingEvent = async ( - prisma: PrismaClient, + prisma: Prisma.TransactionClient, event: Event, - user: User, -): Promise => { - let query: any = { +): Promise => { + const query: Prisma.eventCountArgs = { where: { end: { gt: new Date(event.start) }, - start: { lte: new Date(event.end) }, - room: { hasSome: event.room.map(e => e.toString()) }, + start: { lt: new Date(event.end) }, + room: { hasSome: event.room.map((e) => e.toString()) }, }, }; - if (event.id) { + if (event.id && query.where) { query.where.id = { not: event.id }; } @@ -78,14 +77,15 @@ const bookingTermsAccepted = (event: Event) => { return event.booking_terms; }; -const validPhoneNumber = (phoneNumber: string) => { - return /^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,5}$/im.test( - phoneNumber, +const validPhoneNumber = (phoneNumber: string | null | undefined) => { + return ( + typeof phoneNumber === "string" && + /^[+]?[(]?[0-9]{3}[)]?[-\s.]?[0-9]{3}[-\s.]?[0-9]{4,5}$/.test(phoneNumber) ); }; const validEvent = async ( - prisma: PrismaClient, + prisma: Prisma.TransactionClient, event: Event, user: User, @@ -105,7 +105,7 @@ const validEvent = async ( }; } - if (!userIsInBookingGroup(event, user)) { + if (!event.booked_as.trim() || !userIsInBookingGroup(event, user)) { return { sv: "Bokande grupp ej specificerad", en: "Booking group not specified", @@ -134,13 +134,14 @@ const validEvent = async ( } try { - if (await overlappingEvent(prisma, event, user)) { + if (await overlappingEvent(prisma, event)) { return { sv: "Den angivna tiden är upptagen", en: "The time slot is already taken", }; } } catch (e) { + if (isSerializationFailure(e)) throw e; console.log(e); return { sv: "Kunde inte kontrollera överlappande bokningar", @@ -172,10 +173,49 @@ const toEvent = (event: Event) => ({ end: new Date(event.end), booked_as: event.booked_as, booked_by: event.booked_by || "", - phone: event.phone, - room: event.room.map(e => e.toString()), + phone: event.phone ?? "", + room: event.room.map((e) => e.toString()), }); +const isSerializationFailure = (error: unknown): boolean => { + if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2034") return true; + // Prisma's PostgreSQL driver can surface commit failures directly instead of + // wrapping them as P2034. Its structured kind maps SQLSTATE 40001/40P01; + // matching the message alone could accidentally retry unrelated failures. + return ( + error instanceof globalThis.Error && + error.name === "DriverAdapterError" && + typeof error.cause === "object" && + error.cause !== null && + "kind" in error.cause && + error.cause.kind === "TransactionWriteConflict" + ); +}; + +// Checking availability and writing must share a serializable transaction. +// PostgreSQL then rejects concurrent writes based on the same availability +// snapshot; retrying rechecks the winner's booking before writing again. +export const withBookingTransaction = async ( + prisma: PrismaClient, + operation: (transaction: Prisma.TransactionClient) => Promise, +): Promise => { + for (let attempt = 0; attempt < 3; attempt++) { + try { + return await prisma.$transaction(operation, { + isolationLevel: Prisma.TransactionIsolationLevel.Serializable, + maxWait: 5_000, + timeout: 10_000, + }); + } catch (error) { + if (!isSerializationFailure(error)) throw error; + } + } + return { + sv: "Bokningen ändrades samtidigt. Försök igen.", + en: "A booking changed concurrently. Please try again.", + }; +}; + export const editEvent = async ( prisma: PrismaClient, event: Event, @@ -189,46 +229,30 @@ export const editEvent = async ( }; } - let err = await validEvent(prisma, event, user); - if (err) { - return err; - } - err = await checkRules(prisma, event); - if (err) { - return err; - } - // Getting old event - let old_event = await prisma.event.findFirst({ - where: { id: event.id }, - }); - if (!old_event) { - console.log("Failed to get event with id: " + event.id); - return { - sv: "Kunde inte hämta gamla bokningen", - en: "Failed to get event", - }; - } - - if (!userIsInBookingGroup(old_event, user)) { - return { - sv: "Du har inte behörighet att redigera denna bokning", - en: "You do not have permission to edit this event", + const id = event.id; + return withBookingTransaction(prisma, async (transaction) => { + const previous = await transaction.event.findUnique({ where: { id } }); + if (!previous) return { sv: "Kunde inte hämta gamla bokningen", en: "Failed to get event" }; + if (!userIsInBookingGroup(previous, user)) { + return { + sv: "Du har inte behörighet att redigera denna bokning", + en: "You do not have permission to edit this event", + }; + } + // Group members may edit bookings without access to the author's phone. + // Keep both fields together: changing the author would grant the editor + // access to a phone number that belonged to someone else. + const updated = { + ...event, + phone: event.phone ?? previous.phone, + booked_by: previous.booked_by, }; - } - // Updates event in the database - let res = await prisma.event.update({ - where: { id: event.id }, - data: toEvent(event), + const error = + (await validEvent(transaction, updated, user)) || (await checkRules(transaction, updated)); + if (error) return error; + await transaction.event.update({ where: { id }, data: toEvent(updated) }); + return null; }); - - if (!res) { - return { - sv: "Misslyckades att uppdatera bokning", - en: "Failed to update event", - }; - } - - return null; }; export const createEvent = async ( @@ -236,60 +260,47 @@ export const createEvent = async ( event: Event, user: User, ): Promise => { - // Sanity checks - let err = await validEvent(prisma, event, user); - if (err) { - return err; - } - - err = await checkRules(prisma, event); - if (err) { - return err; - } - - // Adds event in the database - let res = await prisma.event.create({ - data: toEvent(event), - }); - - if (!res) { + if (event.id != null) { return { - sv: "Misslyckades att skapa bokning", - en: "Failed to create event", + sv: "Nya bokningar får inte ange ett befintligt boknings-id", + en: "New bookings must not specify an existing booking ID", }; } - - return null; + return withBookingTransaction(prisma, async (transaction) => { + const error = + (await validEvent(transaction, event, user)) || (await checkRules(transaction, event)); + if (error) return error; + await transaction.event.create({ data: toEvent(event) }); + return null; + }); }; -export const deleteEvent = async ( - prisma: PrismaClient, - id: string, - user: User, -) => { - const event: event | null = await prisma.event.findUnique({ - where: { - id: id, - }, - }); - if (!event) { - return { - sv: "Kunde ej hitta bokningen", - en: "Could not find the event", - }; - } +export const deleteEvent = async (prisma: PrismaClient, id: string, user: User) => { + return withBookingTransaction(prisma, async (transaction) => { + const event: event | null = await transaction.event.findUnique({ + where: { + id: id, + }, + }); + if (!event) { + return { + sv: "Kunde ej hitta bokningen", + en: "Could not find the event", + }; + } - if (!userIsInBookingGroup(event, user)) { - return { - sv: "Du får ej radera denna bokning", - en: "You may not delete this event", - }; - } + if (!userIsInBookingGroup(event, user)) { + return { + sv: "Du får ej radera denna bokning", + en: "You may not delete this event", + }; + } - await prisma.event.delete({ - where: { - id: id, - }, + await transaction.event.delete({ + where: { + id: id, + }, + }); + return null; }); - return null; }; diff --git a/backend/src/services/rule.service.ts b/backend/src/services/rule.service.ts index 0a00a30..0349438 100644 --- a/backend/src/services/rule.service.ts +++ b/backend/src/services/rule.service.ts @@ -1,10 +1,8 @@ import { Event, Rule, Error, User } from "../models"; import { to } from "../utils"; -import { PrismaClient, rule } from "@prisma/client"; +import { Prisma, rule } from "@prisma/client"; import { dbRule } from "../models/rule"; -const MILLISECONDS_24H = 86400000; // == 1000 * 60 * 60 * 24 - /** * A single rule that applies to a specific time slot */ @@ -17,14 +15,6 @@ export interface ExplicitRule { description: string | null; } -const sameDay = (d1: Date, d2: Date): boolean => { - return ( - d1.getFullYear() == d2.getFullYear() && - d1.getMonth() == d2.getMonth() && - d1.getDate() == d2.getDate() - ); -}; - /** * 6 -> "06" * 16 -> "16" @@ -34,9 +24,7 @@ const zero = (n: number): string => { }; export const day = (date: Date): string => { - return `${date.getFullYear()}-${zero(date.getMonth() + 1)}-${zero( - date.getDate(), - )}`; + return `${date.getFullYear()}-${zero(date.getMonth() + 1)}-${zero(date.getDate())}`; }; export const dayApplies = (date: Date, day_mask: number): boolean => { @@ -44,11 +32,7 @@ export const dayApplies = (date: Date, day_mask: number): boolean => { return (day_mask >> dayIndex) % 2 > 0; }; -const insertRule = ( - explicitRules: ExplicitRule[], - current: Date, - rule: rule, -): void => { +const insertRule = (explicitRules: ExplicitRule[], current: Date, rule: rule): void => { explicitRules.push({ start: new Date(day(current) + "T" + rule.start_time), end: new Date(day(current) + "T" + rule.end_time), @@ -56,32 +40,23 @@ const insertRule = ( }); }; -const hasReachedEndOrTo = (current: Date, end: Date, to: Date): boolean => { - return sameDay(current, to) || sameDay(current, end); -}; - /** * Creates a list of explicit rules where each rule apply, i.e., a list of * rules that apply to a specific time slot. */ -export const toExplicitRules = ( - rules: rule[], - from: Date, - to: Date, -): ExplicitRule[] => { +export const toExplicitRules = (rules: rule[], from: Date, to: Date): ExplicitRule[] => { const explicitRules: ExplicitRule[] = []; - for (const rule_i in rules) { - let current = new Date(from); - const end = new Date(rules[rule_i].end_date); - while (true) { - if (dayApplies(current, rules[rule_i].day_mask)) { - insertRule(explicitRules, current, rules[rule_i]); - } - if (hasReachedEndOrTo(current, end, to)) { - break; - } - current = new Date(current.getTime() + MILLISECONDS_24H); + if (!Number.isFinite(from.getTime()) || !Number.isFinite(to.getTime()) || from > to) return []; + for (const rule of rules) { + const current = new Date(Math.max(from.getTime(), rule.start_date.getTime())); + const end = new Date(Math.min(to.getTime(), rule.end_date.getTime())); + current.setHours(0, 0, 0, 0); + end.setHours(0, 0, 0, 0); + while (current <= end) { + if (dayApplies(current, rule.day_mask)) insertRule(explicitRules, current, rule); + // Calendar-day arithmetic also advances correctly across daylight-saving changes. + current.setDate(current.getDate() + 1); } } return explicitRules.sort((a, b): number => a.priority - b.priority); @@ -97,7 +72,7 @@ const mergeIntoList = ( rule: ExplicitRule, [nextMergedRule, ...mergedRules]: ExplicitRule[], ): ExplicitRule[] => { - if (rule.start >= rule.end) return []; + if (rule.start >= rule.end) return nextMergedRule ? [nextMergedRule, ...mergedRules] : []; if (nextMergedRule == undefined) return [rule]; if (nextMergedRule.start > rule.start) { if (nextMergedRule.start >= rule.end) { @@ -112,17 +87,14 @@ const mergeIntoList = ( ]; } if (nextMergedRule.end > rule.start) - return [ - nextMergedRule, - ...mergeIntoList({ ...rule, start: nextMergedRule.end }, mergedRules), - ]; + return [nextMergedRule, ...mergeIntoList({ ...rule, start: nextMergedRule.end }, mergedRules)]; return [nextMergedRule, ...mergeIntoList(rule, mergedRules)]; }; export const mergeRules = (rules: ExplicitRule[]): ExplicitRule[] => { let mergedRules: ExplicitRule[] = []; - for (const i in rules) { - mergedRules = mergeIntoList(rules[i], mergedRules); + for (const rule of rules) { + mergedRules = mergeIntoList(rule, mergedRules); } return mergedRules; }; @@ -133,53 +105,52 @@ const breaksExplicitRule = (rule: ExplicitRule, event: Event): boolean => { return rule.start < end && rule.end > start && !rule.allow; }; -const doesObeyRules = (rules: rule[], event: Event): Error | null => { +export const doesObeyRules = (rules: rule[], event: Event): Error | null => { const start = new Date(event.start); const end = new Date(event.end); - let explicitRules: ExplicitRule[] = mergeRules( - toExplicitRules(rules, start, end), - ); - for (const i in explicitRules) { - if (breaksExplicitRule(explicitRules[i], event)) { - return { - sv: "Bokning bryter regel: " + explicitRules[i].title, - en: "Booking breaks rule: " + explicitRules[i].title, - }; + for (const room of event.room) { + const roomRules = rules.filter((rule) => rule.room.includes(room)); + const explicitRules = mergeRules(toExplicitRules(roomRules, start, end)); + for (const rule of explicitRules) { + if (breaksExplicitRule(rule, event)) { + return { + sv: "Bokning bryter regel: " + rule.title, + en: "Booking breaks rule: " + rule.title, + }; + } } } return null; }; -export const getRulesBetween = async ( - prisma: PrismaClient, - from: Date, - to: Date, -) => { +const stockholmDate = new Intl.DateTimeFormat("sv-SE", { + timeZone: "Europe/Stockholm", + year: "numeric", + month: "2-digit", + day: "2-digit", +}); + +// Rule dates are stored as UTC-midnight calendar dates, not instant cutoffs. +// Include the whole last effective day in the Swedish booking calendar. +export const ruleDateBounds = (from: Date, to: Date) => ({ + end_date: { gte: new Date(`${stockholmDate.format(from)}T00:00:00.000Z`) }, + start_date: { lte: new Date(`${stockholmDate.format(to)}T00:00:00.000Z`) }, +}); + +export const getRulesBetween = async (prisma: Prisma.TransactionClient, from: Date, to: Date) => { return await prisma.rule.findMany({ - where: { - end_date: { - gte: from, - }, - start_date: { - lte: to, - }, - }, + where: ruleDateBounds(from, to), }); }; -export const checkRules = async (prisma: PrismaClient, event: Event) => { +export const checkRules = async (prisma: Prisma.TransactionClient, event: Event) => { const rules = await prisma.rule.findMany({ where: { room: { hasSome: event.room, }, - end_date: { - gte: new Date(event.start), - }, - start_date: { - lte: new Date(event.end), - }, + ...ruleDateBounds(new Date(event.start), new Date(event.end)), }, }); return doesObeyRules(rules, event); @@ -194,7 +165,7 @@ const validDateTime = (start: Date, end: Date): boolean => { }; export const createRule = async ( - prisma: PrismaClient, + prisma: Prisma.TransactionClient, rule: Rule, user: User, ): Promise => { @@ -226,7 +197,14 @@ export const createRule = async ( let res = await prisma.rule.create({ data: { - ...rule, + day_mask: rule.day_mask ?? 0, + start_time: rule.start_time, + end_time: rule.end_time, + description: rule.description, + allow: rule.allow ?? true, + priority: rule.priority ?? 10, + title: rule.title, + room: rule.room, start_date: new Date(rule.start_date), end_date: new Date(rule.end_date), }, @@ -241,10 +219,16 @@ export const createRule = async ( }; export const deleteRule = async ( - prisma: PrismaClient, + prisma: Prisma.TransactionClient, id: string, user: User, ): Promise => { + if (!user.is_admin) { + return { + sv: "Du har inte behörighet att radera regler", + en: "You do not have permission to delete rules", + }; + } const rule: dbRule | null = await prisma.rule.findUnique({ where: { id: id }, }); @@ -255,12 +239,6 @@ export const deleteRule = async ( }; } - if (!user.is_admin) { - return { - sv: "Du har inte behörighet att radera regler", - en: "You do not have permission to delete rules", - }; - } const { err } = await to( prisma.rule.delete({ where: { diff --git a/backend/src/tests/booking-transaction.test.ts b/backend/src/tests/booking-transaction.test.ts new file mode 100644 index 0000000..cd7659e --- /dev/null +++ b/backend/src/tests/booking-transaction.test.ts @@ -0,0 +1,156 @@ +import { PrismaClient, Prisma } from "@prisma/client"; +import { PrismaPg } from "@prisma/adapter-pg"; +import { afterAll, afterEach, describe, expect, it, vi } from "vite-plus/test"; +import { createEvent, editEvent, withBookingTransaction } from "../services/event.service"; +import type { User } from "../models/user"; +import type { Event } from "../models/event"; + +const prisma = new PrismaClient({ + adapter: new PrismaPg({ connectionString: "postgresql://test:test@127.0.0.1:1/test" }), +}); +const conflict = () => + new Prisma.PrismaClientKnownRequestError("Concurrent write", { + code: "P2034", + clientVersion: "7.10.0", + }); +// This is the DriverAdapterError shape emitted by @prisma/adapter-pg for +// PostgreSQL serialization_failure (40001) and deadlock_detected (40P01). +const driverConflict = () => + Object.assign( + new Error("TransactionWriteConflict", { cause: { kind: "TransactionWriteConflict" } }), + { + name: "DriverAdapterError", + }, + ); + +describe("Booking transaction recovery", () => { + afterEach(() => vi.restoreAllMocks()); + afterAll(() => prisma.$disconnect()); + + const user: User = { + cid: "editor", + groups: ["digit"], + is_admin: false, + sub: "editor", + sid: "", + given_name: "", + locale: "en", + picture: "", + name: "Editor", + nickname: "Editor", + family_name: "", + jti: "", + }; + const input: Event = { + id: "booking", + title: "Edited booking", + start: new Date().toISOString(), + end: new Date(Date.now() + 3_600_000).toISOString(), + booked_as: "digit", + booked_by: "editor", + booking_terms: true, + room: ["GROUP_ROOM"], + }; + it.each([undefined, null])( + "preserves the author's phone and identity when edit phone is %s", + async (phone) => { + const previous = { + id: "booking", + title: "Booking", + description: "", + booked_by: "author", + booked_as: "digit", + phone: "0701234567", + room: ["GROUP_ROOM"], + start: new Date(input.start), + end: new Date(input.end), + created_at: new Date(), + updated_at: new Date(), + }; + vi.spyOn(prisma, "$transaction").mockImplementation(async (operation) => operation(prisma)); + vi.spyOn(prisma.event, "findUnique").mockResolvedValue(previous); + vi.spyOn(prisma.event, "count").mockResolvedValue(0); + vi.spyOn(prisma.rule, "findMany").mockResolvedValue([]); + const update = vi.spyOn(prisma.event, "update").mockResolvedValue(previous); + expect(await editEvent(prisma, { ...input, phone }, user)).toBeNull(); + expect(update).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ + phone: "0701234567", + booked_by: "author", + title: "Edited booking", + }), + }), + ); + }, + ); + it("still rejects new bookings without a phone", async () => { + vi.spyOn(prisma, "$transaction").mockImplementation(async (operation) => operation(prisma)); + vi.spyOn(prisma.event, "count").mockResolvedValue(0); + const create = vi.spyOn(prisma.event, "create"); + expect(await createEvent(prisma, { ...input, id: undefined }, user)).toMatchObject({ + en: "Provided phone number is faulty", + }); + expect(create).not.toHaveBeenCalled(); + }); + + it("retries serialization conflicts using serializable isolation", async () => { + const transaction = vi + .spyOn(prisma, "$transaction") + .mockRejectedValueOnce(conflict()) + .mockResolvedValue(null); + expect(await withBookingTransaction(prisma, async () => null)).toBeNull(); + expect(transaction).toHaveBeenCalledTimes(2); + expect(transaction).toHaveBeenCalledWith( + expect.any(Function), + expect.objectContaining({ isolationLevel: "Serializable" }), + ); + }); + + it("bounds retries and returns a useful error when contention persists", async () => { + const transaction = vi.spyOn(prisma, "$transaction").mockRejectedValue(conflict()); + expect(await withBookingTransaction(prisma, async () => null)).toMatchObject({ + en: "A booking changed concurrently. Please try again.", + }); + expect(transaction).toHaveBeenCalledTimes(3); + }); + + it("retries the PostgreSQL adapter's direct commit conflict and returns the rechecked overlap", async () => { + const overlap = { en: "The time slot is already taken", sv: "Den angivna tiden är upptagen" }; + const transaction = vi + .spyOn(prisma, "$transaction") + .mockRejectedValueOnce(driverConflict()) + .mockResolvedValue(overlap); + expect(await withBookingTransaction(prisma, async () => null)).toEqual(overlap); + expect(transaction).toHaveBeenCalledTimes(2); + }); + + it("bounds retries of direct driver conflicts too", async () => { + const transaction = vi.spyOn(prisma, "$transaction").mockRejectedValue(driverConflict()); + expect(await withBookingTransaction(prisma, async () => null)).toMatchObject({ + en: "A booking changed concurrently. Please try again.", + }); + expect(transaction).toHaveBeenCalledTimes(3); + }); + + it.each([ + new Error("TransactionWriteConflict"), + Object.assign(new Error("DatabaseAccessDenied", { cause: { kind: "DatabaseAccessDenied" } }), { + name: "DriverAdapterError", + }), + ])("does not mistake other errors for a structured driver conflict", async (error) => { + const transaction = vi.spyOn(prisma, "$transaction").mockRejectedValue(error); + await expect(withBookingTransaction(prisma, async () => null)).rejects.toBe(error); + expect(transaction).toHaveBeenCalledTimes(1); + }); + + it("does not retry unrelated database failures", async () => { + const transaction = vi + .spyOn(prisma, "$transaction") + .mockRejectedValue(new Error("Connection failed")); + await expect(withBookingTransaction(prisma, async () => null)).rejects.toThrow( + "Connection failed", + ); + expect(transaction).toHaveBeenCalledTimes(1); + }); +}); diff --git a/backend/src/tests/graphql.test.ts b/backend/src/tests/graphql.test.ts new file mode 100644 index 0000000..8decf4a --- /dev/null +++ b/backend/src/tests/graphql.test.ts @@ -0,0 +1,137 @@ +import { join } from "node:path"; +import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vite-plus/test"; +import { ApolloServer } from "@apollo/server"; +import { PrismaClient } from "@prisma/client"; +import { PrismaPg } from "@prisma/adapter-pg"; +import { loadFilesSync } from "@graphql-tools/load-files"; +import { mergeTypeDefs } from "@graphql-tools/merge"; +import { getResolvers } from "../resolvers"; +import type { Context } from "../utils/commonTypes"; + +// Requests in this suite must be rejected before any database access. There is +// deliberately no database listening here; the E2E suite tests actual persistence. +const prisma = new PrismaClient({ + adapter: new PrismaPg({ connectionString: "postgresql://test:test@127.0.0.1:1/test" }), +}); +const server = new ApolloServer({ + typeDefs: mergeTypeDefs(loadFilesSync(join(__dirname, "../schemas/v1/*.gql"))), + resolvers: getResolvers({ prisma }), +}); +const contextValue: Context = { + user: { + sub: "user-id", + cid: "member", + name: "Member", + nickname: "Member", + given_name: "", + family_name: "", + sid: "", + locale: "en", + picture: "", + jti: "", + groups: [], + is_admin: false, + }, +}; + +describe("Apollo GraphQL contract", () => { + afterEach(() => vi.restoreAllMocks()); + it.each([ + { cid: "member", is_admin: false, expected: null }, + { cid: "author", is_admin: false, expected: "0701234567" }, + { cid: "member", is_admin: true, expected: "0701234567" }, + ])( + "protects phones across every booking query for $cid / admin=$is_admin", + async ({ cid, is_admin, expected }) => { + const event = { + id: "booking", + title: "Booking", + description: "", + booked_by: "author", + booked_as: "digit", + phone: "0701234567", + room: ["GROUP_ROOM"], + start: new Date(), + end: new Date(), + created_at: new Date(), + updated_at: new Date(), + }; + vi.spyOn(prisma.event, "findFirst").mockResolvedValue(event); + vi.spyOn(prisma.event, "findMany").mockResolvedValue([event]); + const result = await server.executeOperation( + { + query: `{ + event(id: "booking") { phone } + events { phone } + eventsFT(from: "2026-09-01", to: "2026-09-30") { phone } + }`, + }, + { contextValue: { user: { ...contextValue.user, cid, is_admin, groups: ["digit"] } } }, + ); + if (result.body.kind !== "single") throw new Error("Unexpected incremental response"); + expect(result.body.singleResult.errors).toBeUndefined(); + expect(result.body.singleResult.data).toEqual({ + event: { phone: expected }, + events: [{ phone: expected }], + eventsFT: [{ phone: expected }], + }); + }, + ); + it("rejects a supplied ID when creating a new booking before checking availability", async () => { + const result = await server.executeOperation( + { + query: `mutation { createEvent(event: { + id: "00000000-0000-0000-0000-000000000001", title: "Spoofed booking", + start: "2026-09-10T10:00:00Z", end: "2026-09-10T11:00:00Z", room: [GROUP_ROOM], + phone: "0701234567", booked_as: "digit", booking_terms: true + }) { en } }`, + }, + { contextValue }, + ); + if (result.body.kind !== "single") throw new Error("Unexpected incremental response"); + expect(result.body.singleResult.errors).toBeUndefined(); + expect(result.body.singleResult.data?.createEvent).toMatchObject({ + en: "New bookings must not specify an existing booking ID", + }); + }); + beforeAll(() => server.start()); + afterAll(async () => { + await server.stop(); + await prisma.$disconnect(); + }); + + it("rejects null mutation input through schema validation", async () => { + const result = await server.executeOperation( + { query: "mutation { createEvent(event: null) { en } }" }, + { contextValue }, + ); + expect(result.body.kind).toBe("single"); + if (result.body.kind === "single") + expect(result.body.singleResult.errors?.[0].message).toContain("InputEvent!"); + }); + + it("returns a typed permission error when a member attempts to delete rules", async () => { + const result = await server.executeOperation( + { query: 'mutation { deleteRule(id: "00000000-0000-0000-0000-000000000001") { en sv } }' }, + { contextValue }, + ); + if (result.body.kind !== "single") throw new Error("Unexpected incremental response"); + expect(result.body.singleResult.errors).toBeUndefined(); + expect(result.body.singleResult.data?.deleteRule).toMatchObject({ + en: "You do not have permission to delete rules", + }); + }); + + it("returns the authenticated user's server-side roles and groups", async () => { + const result = await server.executeOperation( + { query: "{ user { cid is_admin groups } }" }, + { contextValue }, + ); + if (result.body.kind !== "single") throw new Error("Unexpected incremental response"); + expect(result.body.singleResult.data?.user).toEqual({ + cid: "member", + is_admin: false, + groups: [], + }); + }); +}); diff --git a/backend/src/tests/rule.test.ts b/backend/src/tests/rule.test.ts index b4be68c..184ef94 100644 --- a/backend/src/tests/rule.test.ts +++ b/backend/src/tests/rule.test.ts @@ -1,11 +1,15 @@ -import assert from "assert"; +import assert from "node:assert/strict"; +import { describe, it } from "vite-plus/test"; import { toExplicitRules, ExplicitRule, day, dayApplies, mergeRules, + doesObeyRules, + ruleDateBounds, } from "../services/rule.service"; +import type { Event } from "../models/event"; import { rule } from "@prisma/client"; const defaultRule: rule = { @@ -61,48 +65,41 @@ describe("Rule utility functions", () => { ); }); it("Should allow day", () => { - assert.equal( - dayApplies(new Date("2021-08-19"), 0b0001000), - true, - "0b0001000", - ); - assert.equal( - dayApplies(new Date("2021-08-19"), 0b0001100), - true, - "0b0001100", - ); - assert.equal( - dayApplies(new Date("2021-08-19"), 0b0011000), - true, - "0b0011000", - ); + assert.equal(dayApplies(new Date("2021-08-19"), 0b0001000), true, "0b0001000"); + assert.equal(dayApplies(new Date("2021-08-19"), 0b0001100), true, "0b0001100"); + assert.equal(dayApplies(new Date("2021-08-19"), 0b0011000), true, "0b0011000"); }); it("Should not allow day", () => { - assert.equal( - dayApplies(new Date("2021-08-19"), 0b1110111), - false, - "0b1110111", - ); - assert.equal( - dayApplies(new Date("2021-08-19"), 0b0000000), - false, - "0b0000000", - ); - assert.equal( - dayApplies(new Date("2021-08-19"), 0b0000111), - false, - "0b0000111", - ); + assert.equal(dayApplies(new Date("2021-08-19"), 0b1110111), false, "0b1110111"); + assert.equal(dayApplies(new Date("2021-08-19"), 0b0000000), false, "0b0000000"); + assert.equal(dayApplies(new Date("2021-08-19"), 0b0000111), false, "0b0000111"); }); }); const assertExplicitRuleEqual = (exp: ExplicitRule, got: ExplicitRule) => { assert.equal(got.start.toISOString(), exp.start.toISOString()); + assert.equal(got.end.toISOString(), exp.end.toISOString()); assert.equal(got.description, exp.description); assert.equal(got.allow, exp.allow); }; describe("Rules to ExplicitRules", () => { + it("returns no rules for an inverted or invalid range", () => { + assert.deepEqual( + toExplicitRules(dummyRules, new Date("2021-08-20"), new Date("2021-08-19")), + [], + ); + assert.deepEqual(toExplicitRules(dummyRules, new Date("invalid"), new Date("2021-08-19")), []); + }); + it("does not expand a rule outside its effective dates", () => { + const rules = [ + { ...dummyRules[0], start_date: new Date("2021-08-19"), end_date: new Date("2021-08-20") }, + ]; + const result = toExplicitRules(rules, new Date("2021-08-01"), new Date("2021-08-31")); + assert.equal(result.length, 2); + assert.equal(day(result[0].start), "2021-08-19"); + assert.equal(day(result[1].start), "2021-08-20"); + }); it("Should create one mini rule", () => { const expected: ExplicitRule[] = [ { @@ -124,7 +121,7 @@ describe("Rules to ExplicitRules", () => { { ...defaultExplicitRules, start: new Date("2021-08-19T08:00"), - end: new Date("2021-08-20T17:00"), + end: new Date("2021-08-19T17:00"), }, { ...defaultExplicitRules, @@ -165,12 +162,88 @@ describe("Rules to ExplicitRules", () => { }); }); +describe("Booking rule scope", () => { + it("includes the complete final calendar day of a rule", () => { + const bounds = ruleDateBounds( + new Date("2026-09-06T12:00:00+02:00"), + new Date("2026-09-06T13:00:00+02:00"), + ); + assert.equal(bounds.end_date.gte.toISOString(), "2026-09-06T00:00:00.000Z"); + assert.equal(bounds.start_date.lte.toISOString(), "2026-09-06T00:00:00.000Z"); + }); + it("uses the Stockholm calendar date even when UTC is on the previous day", () => { + const bounds = ruleDateBounds( + new Date("2026-10-24T22:30:00Z"), + new Date("2026-10-24T23:30:00Z"), + ); + assert.equal(bounds.end_date.gte.toISOString(), "2026-10-25T00:00:00.000Z"); + }); + it("does not let permission in one room override restrictions in another", () => { + const booking: Event = { + title: "Two rooms", + start: "2021-08-20T11:00", + end: "2021-08-20T12:00", + room: ["BIG_HUB", "GROUP_ROOM"], + phone: "0701234567", + booked_by: "member", + booked_as: "digit", + booking_terms: true, + }; + const base = { + ...defaultRule, + start_date: new Date("2021-08-01"), + end_date: new Date("2021-08-31"), + start_time: "08:00", + end_time: "17:00", + }; + const allowed: rule = { + ...base, + title: "Hub permission", + room: ["BIG_HUB"], + allow: true, + priority: 1, + }; + const denied: rule = { + ...base, + title: "Group room closed", + room: ["GROUP_ROOM"], + allow: false, + priority: 10, + }; + assert.equal( + doesObeyRules([allowed, denied], booking)?.en, + "Booking breaks rule: Group room closed", + ); + assert.equal(doesObeyRules([allowed, denied], { ...booking, room: ["BIG_HUB"] }), null); + }); +}); + describe("Merge rules", () => { + it("preserves later restrictions when an inserted interval is already covered", () => { + const first = { + ...defaultExplicitRules, + start: new Date("2021-08-20T08:00"), + end: new Date("2021-08-20T10:00"), + priority: 1, + }; + const later = { + ...defaultExplicitRules, + start: new Date("2021-08-20T14:00"), + end: new Date("2021-08-20T16:00"), + priority: 1, + }; + const covered = { + ...defaultExplicitRules, + start: new Date("2021-08-20T08:00"), + end: new Date("2021-08-20T09:00"), + priority: 2, + allow: true, + }; + assert.deepEqual(mergeRules([first, later, covered]), [first, later]); + }); it("No rules", () => { const expected: ExplicitRule[] = []; - const got = mergeRules( - toExplicitRules([], new Date("2021-08-18"), new Date("2021-08-20")), - ); + const got = mergeRules(toExplicitRules([], new Date("2021-08-18"), new Date("2021-08-20"))); assert.deepEqual(got, expected); }); it("One rule", () => { @@ -182,11 +255,7 @@ describe("Merge rules", () => { }, ]; const got = mergeRules( - toExplicitRules( - [dummyRules[0]], - new Date("2021-08-20"), - new Date("2021-08-21"), - ), + toExplicitRules([dummyRules[0]], new Date("2021-08-20"), new Date("2021-08-21")), ); assert.equal(expected.length, got.length); @@ -229,9 +298,7 @@ describe("Merge rules", () => { end: new Date("2021-08-20T17:00"), }, ]; - const got = mergeRules( - toExplicitRules(rules, new Date("2021-08-20"), new Date("2021-08-20")), - ); + const got = mergeRules(toExplicitRules(rules, new Date("2021-08-20"), new Date("2021-08-20"))); assert.equal(got.length, expected.length); assertExplicitRuleEqual(expected[0], got[0]); assertExplicitRuleEqual(expected[1], got[1]); @@ -278,9 +345,7 @@ describe("Merge rules", () => { end: new Date("2021-08-20T17:00"), }, ]; - const got = mergeRules( - toExplicitRules(rules, new Date("2021-08-20"), new Date("2021-08-20")), - ); + const got = mergeRules(toExplicitRules(rules, new Date("2021-08-20"), new Date("2021-08-20"))); assert.equal(got.length, expected.length); assertExplicitRuleEqual(expected[0], got[0]); assertExplicitRuleEqual(expected[1], got[1]); @@ -343,9 +408,7 @@ describe("Merge rules", () => { end: new Date("2021-08-20T19:00"), }, ]; - const got = mergeRules( - toExplicitRules(rules, new Date("2021-08-20"), new Date("2021-08-20")), - ); + const got = mergeRules(toExplicitRules(rules, new Date("2021-08-20"), new Date("2021-08-20"))); assert.equal(got.length, expected.length); assertExplicitRuleEqual(expected[0], got[0]); assertExplicitRuleEqual(expected[1], got[1]); diff --git a/backend/src/tests/session-store.test.ts b/backend/src/tests/session-store.test.ts new file mode 100644 index 0000000..f1edcd0 --- /dev/null +++ b/backend/src/tests/session-store.test.ts @@ -0,0 +1,66 @@ +import { afterEach, describe, expect, it, vi } from "vite-plus/test"; +import { createSessionStore, type SessionRedis } from "../auth/session-store"; + +const redis = () => ({ + get: vi.fn(), + set: vi.fn().mockResolvedValue("OK"), + del: vi.fn().mockResolvedValue(1), +}); + +describe("OIDC Redis session adapter", () => { + afterEach(() => vi.useRealTimers()); + + it("stores session data using the OIDC expiration and invokes the callback", async () => { + vi.useFakeTimers(); + vi.setSystemTime(1_000_000); + const client = redis(); + const store = createSessionStore(client); + const payload: Parameters[1] = { + header: { iat: 1000, uat: 1000, exp: 1060 }, + data: { + id_token: "test", + access_token: "test", + refresh_token: "", + token_type: "Bearer", + expires_at: "1060", + }, + cookie: { expires: 1_060_000, maxAge: 60_000 }, + }; + await new Promise((resolve, reject) => + store.set("session", payload, (error) => (error ? reject(error) : resolve())), + ); + expect(client.set).toHaveBeenCalledWith("bookit:oidc:session", JSON.stringify(payload), { + EX: 60, + }); + }); + + it("does not restore an expired session even if the Redis key remains", async () => { + const client = redis(); + client.get.mockResolvedValue(JSON.stringify({ header: { exp: 1 }, data: {} })); + const store = createSessionStore(client); + const value = await new Promise((resolve, reject) => + store.get("old", (error, session) => (error ? reject(error) : resolve(session))), + ); + expect(value).toBeNull(); + }); + + it("reports Redis errors instead of silently hanging an authentication request", async () => { + const client = redis(); + client.get.mockRejectedValue(new Error("Redis unavailable")); + const store = createSessionStore(client); + await expect( + new Promise((resolve, reject) => + store.get("session", (error, session) => (error ? reject(error) : resolve(session))), + ), + ).rejects.toThrow("Redis unavailable"); + }); + + it("removes the session on logout", async () => { + const client = redis(); + const store = createSessionStore(client); + await new Promise((resolve, reject) => + store.destroy("session", (error) => (error ? reject(error) : resolve())), + ); + expect(client.del).toHaveBeenCalledWith("bookit:oidc:session"); + }); +}); diff --git a/backend/src/utils/commonTypes.ts b/backend/src/utils/commonTypes.ts index 00979e9..9daf834 100644 --- a/backend/src/utils/commonTypes.ts +++ b/backend/src/utils/commonTypes.ts @@ -1,5 +1,10 @@ import { PrismaClient } from "@prisma/client"; +import { User } from "../models/user"; export interface Tools { prisma: PrismaClient; } + +export interface Context { + user: User; +} diff --git a/backend/src/utils/index.ts b/backend/src/utils/index.ts index 3ea1a8a..178a233 100644 --- a/backend/src/utils/index.ts +++ b/backend/src/utils/index.ts @@ -1,27 +1,15 @@ import axios from "axios"; -export const to = (promise: Promise): Promise<{ err?: any; res?: T }> => { - return promise.then(res => ({ res })).catch(err => ({ err })); -}; - -export const equal = (a: any, b: any) => { - if (a === b) return true; - for (let key in a) { - if (a[key] !== b[key]) return false; - } +export const to = (promise: Promise): Promise<{ err?: unknown; res?: T }> => + promise.then((res) => ({ res })).catch((err: unknown) => ({ err })); - for (let key in b) { - if (a[key] !== b[key]) return false; - } - - return true; -}; - -export const formatDT = (date: Date) => date.toLocaleString("se").slice(0, -3); - -export const authRequest = async (endpoint: string, access_token?: string) => { +export const authRequest = async (endpoint: string, accessToken?: string): Promise => { const headers = { - "Authorization": access_token ? `Bearer ${access_token}` : `pre-shared ${process.env.API_KEY}` - } - return (await axios.get(`${process.env.ISSUER_BASE_URL}${endpoint}`, {headers})).data -} \ No newline at end of file + Authorization: accessToken ? `Bearer ${accessToken}` : `pre-shared ${process.env.API_KEY}`, + }; + const response = await axios.get(`${process.env.ISSUER_BASE_URL}${endpoint}`, { + headers, + timeout: 10_000, + }); + return response.data; +}; diff --git a/backend/startup.sh b/backend/startup.sh index 087cddd..a893f85 100644 --- a/backend/startup.sh +++ b/backend/startup.sh @@ -1,3 +1,10 @@ -export DATABASE_URL=postgres://$DB_USER:$DB_PASS@$DB_HOST:$DB_PORT/$DB_NAME -npm run migrate -node ./build/index.js +#!/bin/sh +set -eu + +# Preserve DB_* deployment configuration; DATABASE_URL can supply escaped credentials. +if [ -z "${DATABASE_URL:-}" ]; then + export DATABASE_URL="postgres://${DB_USER:?}:${DB_PASS:?}@${DB_HOST:?}:${DB_PORT:-5432}/${DB_NAME:?}" +fi + +# Apply database schema changes explicitly as a reviewed release step. +exec node ./build/index.js diff --git a/backend/tsconfig.build.json b/backend/tsconfig.build.json new file mode 100644 index 0000000..48e4631 --- /dev/null +++ b/backend/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "src/tests"] +} diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 6f0a42c..06326f2 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -2,13 +2,17 @@ "exclude": ["node_modules"], "include": ["./src/**/*"], "compilerOptions": { - "target": "ES2015", - "module": "commonjs", + "target": "ES2023", + "module": "NodeNext", + "moduleResolution": "NodeNext", "strict": true, + "noFallthroughCasesInSwitch": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, - "outDir": "./build" + "outDir": "./build", + "rootDir": "./src", + "types": ["node"] }, "$schema": "https://json.schemastore.org/tsconfig", "display": "Recommended" diff --git a/codegen.ts b/codegen.ts new file mode 100644 index 0000000..0667a5e --- /dev/null +++ b/codegen.ts @@ -0,0 +1,52 @@ +import { mkdir, writeFile } from "node:fs/promises"; +import { dirname } from "node:path"; +import { codegen } from "@graphql-codegen/core"; +import * as typescript from "@graphql-codegen/typescript"; +import * as operations from "@graphql-codegen/typescript-operations"; +import * as resolvers from "@graphql-codegen/typescript-resolvers"; +import * as documentNode from "@graphql-codegen/typed-document-node"; +import { loadFilesSync } from "@graphql-tools/load-files"; +import { mergeTypeDefs } from "@graphql-tools/merge"; +import { type DocumentNode } from "graphql"; + +// Local schemas need none of the CLI's remote loaders and deprecated fetch polyfills. +async function generate() { + const schema = mergeTypeDefs(loadFilesSync("backend/src/schemas/v1/*.gql")); + const documents = loadFilesSync("frontend/src/**/*.graphql").map((document) => ({ + document, + })); + const config = { useTypeImports: true, enumsAsTypes: true }; + const outputs = [ + { + filename: "frontend/src/generated/graphql.ts", + content: await codegen({ + schema, + documents, + config, + filename: "frontend/src/generated/graphql.ts", + plugins: [{ operations: {} }, { documentNode: {} }], + pluginMap: { operations, documentNode }, + }), + }, + { + filename: "backend/src/generated/schema.ts", + content: await codegen({ + schema, + documents: [], + filename: "backend/src/generated/schema.ts", + config: { ...config, contextType: "../utils/commonTypes#Context" }, + plugins: [{ typescript: {} }, { resolvers: {} }], + pluginMap: { typescript, resolvers }, + }), + }, + ]; + for (const { filename, content } of outputs) { + await mkdir(dirname(filename), { recursive: true }); + await writeFile(filename, `${content.trimEnd()}\n`); + console.log(`Generated ${filename}`); + } +} +void generate().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/db-scripts/.jobber b/db-scripts/.jobber deleted file mode 100644 index 4d9dd40..0000000 --- a/db-scripts/.jobber +++ /dev/null @@ -1,6 +0,0 @@ -version: 3.1 - -jobs: - my_job: - cmd: psql -h ${DB_HOST} -p ${DB_PORT} -U ${DB_USER} -d ${DB_NAME} < delete-data.sql - time: 0 0 0 # 00:00 every day diff --git a/db-scripts/Dockerfile b/db-scripts/Dockerfile index e6a425d..2f34c2d 100644 --- a/db-scripts/Dockerfile +++ b/db-scripts/Dockerfile @@ -1,18 +1,11 @@ -FROM jobber - -USER root -WORKDIR /home/jobberuser - -RUN apk update -RUN apk add vim postgresql-client - -COPY .jobber ./ -RUN chmod 744 .jobber +FROM postgres:12.22-alpine +WORKDIR /cleanup COPY delete-data.sql ./ - -ENV PGPASSWORD=password +COPY cleanup.sh ./ +USER postgres ENV DB_USER=postgres ENV DB_HOST=db ENV DB_PORT=5432 ENV DB_NAME=bookit +ENTRYPOINT ["sh", "/cleanup/cleanup.sh"] diff --git a/db-scripts/cleanup.sh b/db-scripts/cleanup.sh new file mode 100644 index 0000000..0e2601b --- /dev/null +++ b/db-scripts/cleanup.sh @@ -0,0 +1,14 @@ +#!/bin/sh +# Retain the daily privacy cleanup without the unsupported Jobber image. +set -eu +trap 'exit 0' INT TERM +while :; do + if psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -f /cleanup/delete-data.sql; then + delay=86400 + else + echo "BookIT privacy cleanup failed; retrying in one hour" >&2 + delay=3600 + fi + sleep "$delay" & + wait "$!" +done diff --git a/docker-compose.yml b/docker-compose.yml index 5b8f665..eb531a0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,19 +1,18 @@ -version: "3" services: db: - image: postgres:12 + image: postgres:12.22-alpine environment: POSTGRES_DB: bookit POSTGRES_USER: postgres POSTGRES_PASSWORD: password TZ: "Europe/Stockholm" ports: - - 5432:5432 + - 127.0.0.1:5432:5432 redis: - image: redis:5.0 + image: redis:5.0.14-alpine ports: - - 6379:6379 + - 127.0.0.1:6379:6379 db-scripts: # Deletes personal data from database build: diff --git a/e2e/README.md b/e2e/README.md new file mode 100644 index 0000000..6533a02 --- /dev/null +++ b/e2e/README.md @@ -0,0 +1,39 @@ +# BookIT browser tests + +The suite exercises the real BookIT backend and frontend against a real Gamma +identity provider. `compose.ts` owns the complete environment: an isolated Docker +network, two PostgreSQL containers, two Redis containers, Gamma, Prisma schema +initialization, and the application processes. Ports are assigned per run. It +does not read another checkout or use a developer's database or credentials. + +Install dependencies and Playwright Chromium, then use `pnpm test:e2e`. Docker +must be running. A worker shares the container environment, while each test gets +a fresh browser context and cleared BookIT bookings/rules. Run with one worker +to keep the suite's resource usage predictable. + +For the manual smoke test, run `pnpm e2e:dev` and open the printed BookIT URL. +The command prints synthetic login credentials and keeps the environment alive. +Ctrl+C stops application processes and removes the test containers and network. + +Tests import the extended `test` and `expect` from `./fixtures`. The default page +logs in through Gamma as `member` before the test starts. A spec can use +`test.use({ role: "admin" })` or `test.use({ role: "outsider" })`; `loginAs` lets a +test explicitly switch identity. `authenticate: false` is reserved for tests of +the unauthenticated flow. + +The synthetic admin has the BookIT client's `admin` authority, configured through +Gamma's UI. Admin and member belong to digIT; outsider belongs to no group. +Gamma's global bootstrap administrator is used only to provision the OAuth +client and its CLIENT API key. Authentication and authorization are never mocked. + +Image versions are explicit in `compose.ts`. BookIT uses PostgreSQL 12.22 and +Redis 5.0.14 to cover its intentionally deferred server upgrades. Gamma 2.5.1 is +pinned by digest, with PostgreSQL 16.0 and Redis 5.0.14 matching the working +`chalmers.it` `test/gamma-integration` reference. These pins are compatibility +baselines, not a claim that the old PostgreSQL/Redis releases receive security +fixes. Upgrade the BookIT servers in their separate infrastructure PRs and update +these test pins alongside them. + +On failure, Playwright retains browser artifacts and the fixture attaches bounded +application/Gamma logs with bootstrap credentials redacted. Startup failures print +those logs before cleaning up the owned environment. diff --git a/e2e/admin-rules.spec.ts b/e2e/admin-rules.spec.ts new file mode 100644 index 0000000..ca64708 --- /dev/null +++ b/e2e/admin-rules.spec.ts @@ -0,0 +1,58 @@ +import { test, expect } from "./fixtures"; +import { bookingDate } from "./booking-helpers"; + +test.use({ role: "admin" }); + +test("an administrator creates, views and deletes a recurring booking rule", async ({ page }) => { + await page.getByRole("navigation").getByRole("link", { name: "Rules", exact: true }).click(); + await expect(page.getByRole("heading", { name: "Rules", exact: true })).toBeVisible(); + await page.getByRole("button", { name: "New rule", exact: true }).click(); + const form = page.getByRole("dialog", { name: "New rule", exact: true }); + await expect(form.getByLabel("Priority", { exact: true })).toHaveValue("10"); + await expect(form.getByLabel("Availability", { exact: true })).toHaveValue("true"); + await expect(form.getByLabel("End date", { exact: true })).toHaveValue("2040-12-31"); + await expect(form.getByRole("checkbox", { checked: true })).toHaveCount(0); + await form.getByLabel("Title", { exact: true }).fill("E2E room maintenance"); + await form.getByLabel("Priority", { exact: true }).fill("2"); + await form.getByLabel("Availability", { exact: true }).selectOption("false"); + await form.getByLabel("Start date", { exact: true }).fill(await bookingDate(page)); + await form.getByLabel("End date", { exact: true }).fill("2040-12-31"); + await form.getByLabel("Start time", { exact: true }).fill("12:00"); + await form.getByLabel("End time", { exact: true }).fill("13:00"); + await form.getByRole("checkbox", { name: "Storhubben", exact: true }).check(); + await form.getByLabel("Description", { exact: true }).fill("The room is closed for maintenance."); + await form.getByRole("button", { name: "Save rule", exact: true }).click(); + await expect(form.getByRole("alert")).toHaveText("Select rooms and at least one weekday."); + for (const weekday of ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]) { + await form.getByRole("checkbox", { name: weekday, exact: true }).check(); + } + await form.getByRole("button", { name: "Save rule", exact: true }).click(); + await expect(form).not.toBeVisible(); + await page.reload(); + const row = page.getByRole("row").filter({ hasText: "E2E room maintenance" }); + await expect(row).toContainText("Storhubben"); + await expect(row).toContainText("12:00–13:00"); + await expect(row).toContainText("Blocked"); + await expect(row).toContainText("Mon · Tue · Wed · Thu · Fri · Sat · Sun"); + await row.getByRole("button", { name: "Details E2E room maintenance", exact: true }).click(); + const details = page.getByRole("dialog", { name: "Rule details", exact: true }); + await expect( + details.getByText("The room is closed for maintenance.", { exact: true }), + ).toBeVisible(); + await expect( + details.getByText("Created", { exact: true }).locator("..").locator("dd"), + ).toContainText(/\d{4}/); + await expect( + details.getByText("Updated", { exact: true }).locator("..").locator("dd"), + ).toContainText(/\d{4}/); + await details.press("Escape"); + await expect(details).not.toBeVisible(); + await row.getByRole("button", { name: "Delete E2E room maintenance", exact: true }).click(); + await page + .getByRole("dialog", { name: "Delete rule?" }) + .getByRole("button", { name: "Confirm deletion", exact: true }) + .click(); + await expect(row).toHaveCount(0); + await page.reload(); + await expect(page.getByText("No booking rules yet.", { exact: true })).toBeVisible(); +}); diff --git a/e2e/booking-helpers.ts b/e2e/booking-helpers.ts new file mode 100644 index 0000000..fb7c434 --- /dev/null +++ b/e2e/booking-helpers.ts @@ -0,0 +1,70 @@ +import { expect, type Page } from "@playwright/test"; + +export async function bookingDate(page: Page): Promise { + // Match the browser's date, including CI's configured timezone. + return page.evaluate(() => { + const now = new Date(); + return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`; + }); +} + +export async function acceptConditions(page: Page) { + await page.getByRole("checkbox", { name: /I accept the booking terms/ }).check(); + await page.getByRole("checkbox", { name: /I accept the privacy agreement/ }).check(); + await page.getByRole("checkbox", { name: /I have notified Cubsec/ }).check(); +} + +export async function fillBooking(page: Page, title: string) { + const date = await bookingDate(page); + await page.getByLabel("Title", { exact: true }).fill(title); + await page.getByLabel("Phone number", { exact: true }).fill("0701234567"); + await expect(page.getByLabel("Booking as", { exact: true })).toHaveValue(""); + await expect(page.getByRole("checkbox", { name: "Storhubben", exact: true })).toBeChecked(); + await page.getByLabel("Booking as", { exact: true }).selectOption("digit"); + await page.getByRole("checkbox", { name: "Storhubben", exact: true }).check(); + await page.getByLabel("Begins at", { exact: true }).fill(`${date}T12:00`); + await page.getByLabel("Ends at", { exact: true }).fill(`${date}T13:00`); + await page + .getByLabel("Description", { exact: true }) + .fill("Planning together in our shared room."); + await acceptConditions(page); +} + +export async function createBooking(page: Page, title: string) { + await page.goto("/new-event"); + await fillBooking(page, title); + await page.getByRole("button", { name: "Save booking", exact: true }).click(); + await expect(page).toHaveURL(/\/$/); + await expect(page.getByRole("button").filter({ hasText: title }).first()).toBeVisible(); +} + +export async function openBooking(page: Page, title: string): Promise { + await page.getByRole("button").filter({ hasText: title }).first().click(); + await expect(page.getByRole("dialog", { name: "Booking details", exact: true })).toBeVisible(); + await expect(page.getByRole("heading", { name: title, exact: true })).toBeVisible(); + const result = await graphql<{ events: { id: string; title: string }[] }>( + page, + "{ events { id title } }", + ); + const id = result.events.find((event) => event.title === title)?.id; + if (!id) throw new Error("Booking details did not include a booking id"); + await page.goto(`/bookings/${id}`); + await expect(page.getByRole("heading", { name: title, exact: true })).toBeVisible(); + return id; +} + +export async function graphql( + page: Page, + query: string, + variables: Record = {}, +): Promise { + const response = await page.request.post("/api/graphql/v1", { + headers: { Origin: new URL(page.url()).origin }, + data: { query, variables }, + }); + expect(response.status()).toBe(200); + const body = (await response.json()) as { data?: T; errors?: unknown[] }; + expect(body.errors).toBeUndefined(); + if (!body.data) throw new Error("GraphQL response has no data"); + return body.data; +} diff --git a/e2e/booking-lifecycle.spec.ts b/e2e/booking-lifecycle.spec.ts new file mode 100644 index 0000000..72ac21e --- /dev/null +++ b/e2e/booking-lifecycle.spec.ts @@ -0,0 +1,92 @@ +import { test, expect } from "./fixtures"; +import { + acceptConditions, + bookingDate, + createBooking, + graphql, + openBooking, +} from "./booking-helpers"; +import type { + CreateBookingMutation, + DeleteBookingMutation, +} from "../frontend/src/generated/graphql"; + +test("a group member creates, edits and deletes a persisted booking", async ({ page }) => { + const title = "E2E digIT planning"; + await createBooking(page, title); + const id = await openBooking(page, title); + await expect( + page.getByText("Planning together in our shared room.", { exact: true }), + ).toBeVisible(); + await page.reload(); + await expect(page.getByRole("heading", { name: title, exact: true })).toBeVisible(); + + await page.getByRole("button", { name: "Edit", exact: true }).click(); + await expect(page.getByLabel("Phone number", { exact: true })).toHaveValue("0701234567"); + await page.getByLabel("Title", { exact: true }).fill(`${title} updated`); + await page.getByLabel("Description", { exact: true }).fill("Agenda updated by a group member."); + await acceptConditions(page); + await page.getByRole("button", { name: "Save booking", exact: true }).click(); + await expect(page).toHaveURL(/\/$/); + await openBooking(page, `${title} updated`); + await expect(page).toHaveURL((url) => url.pathname === `/bookings/${id}`); + await expect(page.getByText("Agenda updated by a group member.", { exact: true })).toBeVisible(); + + await page.getByRole("button", { name: "Delete", exact: true }).click(); + const dialog = page.getByRole("dialog", { name: "Delete booking?" }); + await dialog.getByRole("button", { name: "Cancel", exact: true }).click(); + await expect(page.getByRole("heading", { name: `${title} updated`, exact: true })).toBeVisible(); + await page.getByRole("button", { name: "Delete", exact: true }).click(); + await dialog.getByRole("button", { name: "Confirm deletion", exact: true }).click(); + await expect(page).toHaveURL(/\/$/); + await expect(page.getByRole("button").filter({ hasText: title })).toHaveCount(0); + await page.goto(`/bookings/${id}`); + await expect(page.getByRole("alert")).toHaveText("Booking not found."); + + // These requests race over HTTP against the real PostgreSQL-backed service. + // Checking availability and inserting must form one atomic operation. + const date = await bookingDate(page); + const titles = ["E2E concurrent booking A", "E2E concurrent booking B"]; + const outcomes = await Promise.all( + titles.map((concurrentTitle) => + graphql( + page, + "mutation($event: InputEvent!) { createEvent(event: $event) { en sv } }", + { + event: { + title: concurrentTitle, + phone: "0701234567", + booked_as: "digit", + booking_terms: true, + start: `${date}T15:00:00.000Z`, + end: `${date}T16:00:00.000Z`, + room: ["CTC"], + }, + }, + ), + ), + ); + expect(outcomes.filter((outcome) => outcome.createEvent === null)).toHaveLength(1); + expect( + outcomes.filter((outcome) => outcome.createEvent?.en === "The time slot is already taken"), + ).toHaveLength(1); + const persisted = await graphql<{ events: { id: string; title: string }[] }>( + page, + "{ events { id title } }", + ); + const concurrentBookings = persisted.events.filter((booking) => titles.includes(booking.title)); + expect(concurrentBookings).toHaveLength(1); + const winner = concurrentBookings[0]; + if (!winner) throw new Error("The successful concurrent booking was not persisted"); + const removed = await graphql( + page, + "mutation($id: String!) { deleteEvent(id: $id) { en sv } }", + { id: winner.id }, + ); + expect(removed.deleteEvent).toBeNull(); + const afterCleanup = await graphql<{ events: { id: string; title: string }[] }>( + page, + "{ events { id title } }", + ); + expect(afterCleanup.events.filter((booking) => titles.includes(booking.title))).toHaveLength(0); +}); diff --git a/e2e/calendar.spec.ts b/e2e/calendar.spec.ts new file mode 100644 index 0000000..db63f6d --- /dev/null +++ b/e2e/calendar.spec.ts @@ -0,0 +1,46 @@ +import { test, expect } from "./fixtures"; +import { createBooking } from "./booking-helpers"; + +test("calendar filtering, period navigation and language work on desktop and mobile", async ({ + page, +}) => { + const title = "E2E calendar meeting"; + await createBooking(page, title); + const event = page.getByRole("button").filter({ hasText: title }); + const roomFilter = page.getByRole("button", { name: "Storhubben", exact: true }); + await expect(roomFilter).toHaveAttribute("aria-pressed", "true"); + await roomFilter.click(); + await expect(roomFilter).toHaveAttribute("aria-pressed", "false"); + await expect(event).toHaveCount(0); + await roomFilter.click(); + await expect(event.first()).toBeVisible(); + + const period = page.getByRole("heading", { level: 3, name: /\d{4}/ }); + const originalPeriod = await period.innerText(); + await page.getByRole("button", { name: /next/i }).click(); + await expect(period).not.toHaveText(originalPeriod); + await expect(event).toHaveCount(0); + await page.getByRole("button", { name: /previous/i }).click(); + await expect(period).toHaveText(originalPeriod); + await expect(event.first()).toBeVisible(); + + await page.getByRole("button", { name: "Switch to Swedish", exact: true }).click(); + await expect(page.getByRole("heading", { name: "Kalender", exact: true })).toBeAttached(); + await page.reload(); + await expect(page.getByRole("button", { name: "Byt till engelska", exact: true })).toBeVisible(); + await page.getByRole("button", { name: "Byt till engelska", exact: true }).click(); + + await page.setViewportSize({ width: 390, height: 844 }); + await page.reload(); + await expect(page.getByRole("heading", { name: "Calendar", exact: true })).toBeAttached(); + await expect(period).not.toHaveText(originalPeriod); + const mobilePeriod = await period.innerText(); + await expect(page.getByRole("grid", { name: mobilePeriod, exact: true })).toBeVisible(); + await expect(page.getByRole("tab", { name: /list/i })).toHaveAttribute("aria-selected", "false"); + await expect(event.first()).toBeVisible(); + await expect(page.getByRole("link", { name: "New booking", exact: true })).toBeVisible(); + const overflow = await page.evaluate( + () => document.documentElement.scrollWidth - window.innerWidth, + ); + expect(overflow).toBeLessThanOrEqual(1); +}); diff --git a/e2e/compose.ts b/e2e/compose.ts new file mode 100644 index 0000000..f117084 --- /dev/null +++ b/e2e/compose.ts @@ -0,0 +1,400 @@ +import { expect, type Browser, type Page } from "@playwright/test"; +import { PostgreSqlContainer, type StartedPostgreSqlContainer } from "@testcontainers/postgresql"; +import { GenericContainer, Network, Wait, type StartedTestContainer } from "testcontainers"; +import { spawn, type ChildProcess } from "node:child_process"; +import { createServer, type AddressInfo } from "node:net"; +import { resolve } from "node:path"; +import { setTimeout as delay } from "node:timers/promises"; + +const root = resolve(__dirname, ".."); + +// BookIT intentionally retains its deployed database/server major versions. +// Gamma matches the working chalmers.it test/gamma-integration reference. +export const images = { + bookitPostgres: "postgres:12.22-alpine", + bookitRedis: "redis:5.0.14-alpine", + gammaPostgres: "postgres:16.0-alpine", + gammaRedis: "redis:5.0.14-alpine", + gamma: + "ghcr.io/cthit/gamma:2.5.1@sha256:5112c5673ee5c98b072c38afe4ed0de5f7f7d6ae85c41bbd318c92f0e3d5d9db", +} as const; + +// Synthetic identities are bootstrapped into the real Gamma application. +export const users = { + admin: { + id: "88eec5c2-5ebb-4e13-9a76-fcc4dac9e74f", + cid: "bookadmin", + nick: "BookIT Admin", + groups: ["digit"], + }, + member: { + id: "bc605869-9a4d-46ec-8a29-d00819d4c195", + cid: "bookmember", + nick: "BookIT Member", + groups: ["digit"], + }, + outsider: { + id: "ec8987d7-4087-461d-bed5-9365086b6e3b", + cid: "bookguest", + nick: "BookIT Guest", + groups: [], + }, +} as const; +export type UserRole = keyof typeof users; +export const testPassword = "password1337"; + +const groupId = "aed27030-ad90-4526-855c-1e909b1dcecb"; +const postId = "7bb1db15-730d-4864-bfc3-99abe7c0ccf8"; +const seed = { + users: Object.values(users).map((user) => ({ + id: user.id, + cid: user.cid, + nick: user.nick, + firstName: "BookIT", + lastName: user.cid, + acceptanceYear: 2020, + })), + superGroups: [{ id: groupId, name: "digit", prettyName: "digIT", type: "COMMITTEE" }], + groups: [ + { + id: "acd27030-ad90-4526-855c-1e909b1dcecb", + name: "digit-test", + prettyName: "digIT test", + superGroupId: groupId, + members: [users.admin, users.member].map((user) => ({ userId: user.id, postId })), + }, + ], + posts: [{ id: postId, postName: { sv: "Testmedlem", en: "Test member" } }], +}; + +export interface Environment { + appUrl: string; + gammaUrl: string; + logs: Record; + resetBookings(): Promise; + stop(): Promise; +} + +async function unusedPort(): Promise { + return new Promise((resolve, reject) => { + const server = createServer(); + server.on("error", reject); + server.listen(0, "127.0.0.1", () => { + const port = (server.address() as AddressInfo).port; + server.close((error) => (error ? reject(error) : resolve(port))); + }); + }); +} + +async function stopProcess(child: ChildProcess): Promise { + if (!child.pid || child.exitCode !== null || child.signalCode !== null) return; + const exited = new Promise((resolve) => child.once("exit", () => resolve())); + process.kill(-child.pid, "SIGTERM"); + await Promise.race([exited, delay(10_000)]); + if (child.exitCode === null && child.signalCode === null) { + process.kill(-child.pid, "SIGKILL"); + await exited; + } +} + +async function provisionClient(browser: Browser, gammaUrl: string, appUrl: string) { + const context = await browser.newContext(); + try { + const page = await context.newPage(); + page.setDefaultTimeout(30_000); + await page.goto(`${gammaUrl}/login`); + await page.locator('[name="username"]').fill("admin"); + await page.locator('[name="password"]').fill(testPassword); + await page.getByRole("button", { name: "Login", exact: true }).click(); + await expect(page.getByText("Hey, admin!")).toBeVisible(); + await page.goto(`${gammaUrl}/clients/create`); + await page.locator('[name="prettyName"]').fill("BookIT isolated E2E"); + await page.locator('[name="svDescription"]').fill("Lokalt integrationstest"); + await page.locator('[name="enDescription"]').fill("Isolated BookIT integration test"); + await page.locator('[name="redirectUrl"]').fill(`${appUrl}/api/callback`); + await page.locator('[name="generateApiKey"]').check(); + await page.getByRole("button", { name: "Create", exact: true }).click(); + await expect(page.getByText("Client details", { exact: true })).toBeVisible(); + const clientId = (await page.locator('li:has-text("Client id:") span').innerText()).trim(); + const credentials = page + .locator("article") + .filter({ has: page.locator("header", { hasText: /^\s*Credentials\s*$/ }) }); + const clientSecret = (await credentials.locator("code").first().innerText()).trim(); + const authorization = await credentials + .locator("code") + .filter({ hasText: "Authorization: pre-shared" }) + .innerText(); + const apiKey = authorization.replace("Authorization: pre-shared", "").trim(); + if (!clientId || !clientSecret || !/^[\w-]+:\S+$/.test(apiKey)) { + throw new Error("Gamma did not return complete OAuth and CLIENT API credentials"); + } + // BookIT admin means this client's authority, not Gamma's global admin flag. + await page.locator('[name="authority"]').fill("admin"); + await page.getByRole("button", { name: "Add user authority", exact: true }).click(); + await page.locator(".authority-user-row").selectOption(users.admin.id); + await page.getByRole("button", { name: "Save", exact: true }).click(); + await expect( + page.locator("article").filter({ has: page.locator("header", { hasText: /^\s*admin\s*$/ }) }), + ).toBeVisible(); + return { clientId, clientSecret, apiKey }; + } finally { + await context.close(); + } +} + +export async function loginAs(page: Page, environment: Environment, role: UserRole): Promise { + await page.context().clearCookies(); + await page.goto(`${environment.appUrl}/api/login`); + await page.waitForURL((url) => url.origin === environment.gammaUrl && url.pathname === "/login"); + await page.locator('[name="username"]').fill(users[role].cid); + await page.locator('[name="password"]').fill(testPassword); + await page.getByRole("button", { name: "Login", exact: true }).click(); + await Promise.race([ + page.waitForURL((url) => url.origin === environment.appUrl), + page.getByRole("button", { name: "Authorize", exact: true }).waitFor(), + ]); + if (new URL(page.url()).origin === environment.gammaUrl) { + await page.getByRole("button", { name: "Authorize", exact: true }).click(); + } + await page.waitForURL( + (url) => url.origin === environment.appUrl && url.pathname !== "/api/callback", + ); +} + +export async function compose(browser: Browser): Promise { + const network = await new Network().start(); + const containers: StartedTestContainer[] = []; + const processes: ChildProcess[] = []; + const logs: Record = {}; + let stopped = false; + const record = (name: string, chunk: Buffer | string) => { + // Gamma's development bootstrap prints synthetic credentials. Keep them out of CI artifacts. + const text = String(chunk) + .replace(/(password:)\S+/g, "$1[REDACTED]") + .replace(/(and code:)\s*\S+/g, "$1 [REDACTED]"); + logs[name] = ((logs[name] ?? "") + text).slice(-100_000); + }; + const track = async (pending: Promise): Promise => { + const container = await pending; + containers.push(container); + return container; + }; + const stop = async () => { + if (stopped) return; + stopped = true; + const failures: unknown[] = []; + for (const child of processes.toReversed()) { + try { + await stopProcess(child); + } catch (error) { + failures.push(error); + } + } + for (const container of containers.toReversed()) { + try { + await container.stop(); + } catch (error) { + failures.push(error); + } + } + try { + await network.stop(); + } catch (error) { + failures.push(error); + } + if (failures.length) throw new AggregateError(failures, "E2E environment cleanup failed"); + }; + const startProcess = (name: string, args: string[], env: Record) => { + const child = spawn("pnpm", args, { + cwd: root, + env: { ...process.env, ...env }, + detached: true, + stdio: ["ignore", "pipe", "pipe"], + }); + processes.push(child); + child.stdout?.on("data", (chunk: Buffer) => record(name, chunk)); + child.stderr?.on("data", (chunk: Buffer) => record(name, chunk)); + child.on("error", (error) => record(name, error.message)); + child.on("exit", (code) => { + if (code !== null && code !== 0 && !stopped) + console.error(`[${name}] exited with code ${code}\n${logs[name] ?? ""}`); + }); + return child; + }; + try { + console.log("Starting isolated PostgreSQL, Redis and Gamma containers..."); + const gammaDb = await track( + new PostgreSqlContainer(images.gammaPostgres) + .withNetwork(network) + .withNetworkAliases("gamma-db") + .withDatabase("gamma_test") + .withUsername("gamma_test") + .withPassword("gamma_test") + .start(), + ); + await track( + new GenericContainer(images.gammaRedis) + .withNetwork(network) + .withNetworkAliases("gamma-redis") + .withWaitStrategy(Wait.forLogMessage("Ready to accept connections")) + .start(), + ); + const bookitDb: StartedPostgreSqlContainer = await track( + new PostgreSqlContainer(images.bookitPostgres) + .withNetwork(network) + .withNetworkAliases("bookit-db") + .withDatabase("bookit_test") + .withUsername("bookit_test") + .withPassword("bookit_test") + .start(), + ); + const bookitRedis = await track( + new GenericContainer(images.bookitRedis) + .withNetwork(network) + .withNetworkAliases("bookit-redis") + .withExposedPorts(6379) + .withWaitStrategy(Wait.forLogMessage("Ready to accept connections")) + .start(), + ); + const gammaPort = await unusedPort(); + const gammaUrl = `http://localhost:${gammaPort}`; + const appPort = await unusedPort(); + const appUrl = `http://localhost:${appPort}`; + const frontendPort = await unusedPort(); + await track( + new GenericContainer(images.gamma) + .withPlatform("linux/amd64") + .withResourcesQuota({ memory: 2 }) + .withNetwork(network) + .withNetworkAliases("gamma") + .withEnvironment({ + DB_HOST: "gamma-db", + DB_NAME: gammaDb.getDatabase(), + DB_USER: gammaDb.getUsername(), + DB_PASSWORD: gammaDb.getPassword(), + REDIS_HOST: "gamma-redis", + SERVER_PORT: "8081", + BASE_URL: gammaUrl, + PRODUCTION: "false", + IS_MOCKING: "true", + ADMIN_SETUP: "true", + MOCK_DATA_RESOURCE: "file:/tmp/bookit-gamma-seed.json", + UPLOAD_FOLDER: "/tmp/uploads/", + }) + .withCopyContentToContainer([ + { content: JSON.stringify(seed), target: "/tmp/bookit-gamma-seed.json" }, + ]) + .withExposedPorts({ container: 8081, host: gammaPort }) + .withLogConsumer((stream) => stream.on("data", (chunk: Buffer) => record("gamma", chunk))) + .withWaitStrategy( + Wait.forAll([ + Wait.forHttp("/login", 8081).forStatusCode(200), + Wait.forLogMessage(/Api key of type INFO has been generated/), + ]), + ) + .withStartupTimeout(240_000) + .start(), + ); + console.log("Provisioning BookIT OAuth client and roles through Gamma..."); + const client = await provisionClient(browser, gammaUrl, appUrl); + const env = { + NODE_ENV: "test", + TZ: "Europe/Stockholm", + DATABASE_URL: bookitDb.getConnectionUri(), + REDIS_HOST: bookitRedis.getHost(), + REDIS_PORT: String(bookitRedis.getMappedPort(6379)), + REDIS_PASS: "", + SESSION_SECRET: "bookit-isolated-e2e-session-secret", + SECRET: "bookit-isolated-e2e-oidc-secret-at-least-32-characters", + BASE_URL: appUrl, + BACKEND_URL: appUrl, + PORT: String(appPort), + FRONTEND_URL: `http://127.0.0.1:${frontendPort}`, + ISSUER_BASE_URL: gammaUrl, + CLIENT_ID: client.clientId, + CLIENT_SECRET: client.clientSecret, + API_KEY: client.apiKey, + }; + console.log("Generating Prisma client and initializing the isolated BookIT schema..."); + for (const command of [["generate"], ["db", "push"]]) { + const migrate = startProcess( + "schema", + ["--dir", "backend", "exec", "prisma", ...command], + env, + ); + await new Promise((resolve, reject) => { + migrate.once("error", reject); + migrate.once("exit", (code) => + code === 0 + ? resolve() + : reject(new Error(`BookIT schema setup failed: ${logs.schema ?? ""}`)), + ); + }); + } + const frontend = startProcess( + "frontend", + [ + "--dir", + "frontend", + "dev", + "--host", + "127.0.0.1", + "--port", + String(frontendPort), + "--strictPort", + ], + env, + ); + const backend = startProcess("backend", ["--dir", "backend", "start"], env); + await expect + .poll( + async () => { + for (const child of [frontend, backend]) { + if (child.exitCode !== null || child.signalCode !== null) { + throw new Error( + `BookIT exited during startup.\n${logs.backend ?? ""}\n${logs.frontend ?? ""}`, + ); + } + } + const front = await fetch(env.FRONTEND_URL, { signal: AbortSignal.timeout(2_000) }).catch( + () => undefined, + ); + await front?.body?.cancel(); + const back = await fetch(`${appUrl}/api/health`, { + redirect: "manual", + signal: AbortSignal.timeout(2_000), + }).catch(() => undefined); + await back?.body?.cancel(); + return front?.status === 200 && back?.status === 200; + }, + { timeout: 120_000, intervals: [500, 1000] }, + ) + .toBe(true); + return { + appUrl, + gammaUrl, + logs, + stop, + async resetBookings() { + const result = await bookitDb.exec([ + "psql", + "--username", + "bookit_test", + "--dbname", + "bookit_test", + "--set", + "ON_ERROR_STOP=1", + "--command", + "TRUNCATE TABLE event, rule;", + ]); + if (result.exitCode !== 0) + throw new Error(`Could not reset isolated BookIT database: ${result.output}`); + }, + }; + } catch (error) { + for (const [name, log] of Object.entries(logs)) + console.error(`[${name}]\n${log.slice(-8_000)}`); + await stop(); + throw error; + } +} diff --git a/e2e/dev.ts b/e2e/dev.ts new file mode 100644 index 0000000..881dadd --- /dev/null +++ b/e2e/dev.ts @@ -0,0 +1,43 @@ +import { chromium } from "@playwright/test"; +import { compose, testPassword, users } from "./compose"; + +async function main() { + const browser = await chromium.launch(); + let stopEnvironment: (() => Promise) | undefined; + let stopping = false; + async function stop() { + if (stopping) return; + stopping = true; + try { + await stopEnvironment?.(); + } finally { + await browser.close(); + } + } + process.once("SIGINT", () => { + void stop(); + }); + process.once("SIGTERM", () => { + void stop(); + }); + try { + const environment = await compose(browser); + stopEnvironment = () => environment.stop(); + console.log(`BookIT smoke environment: ${environment.appUrl}`); + console.log(`Gamma: ${environment.gammaUrl}`); + console.log( + `Synthetic users: ${Object.values(users) + .map((user) => user.cid) + .join(", ")}; password: ${testPassword}`, + ); + console.log("Press Ctrl+C to stop and remove this isolated environment."); + } catch (error) { + await stop(); + throw error; + } +} + +void main().catch((error: unknown) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/e2e/fixtures.ts b/e2e/fixtures.ts new file mode 100644 index 0000000..c15b534 --- /dev/null +++ b/e2e/fixtures.ts @@ -0,0 +1,48 @@ +import { test as base, expect } from "@playwright/test"; +import { compose, loginAs, type Environment, type UserRole } from "./compose"; + +type TestFixtures = { + role: UserRole; + authenticate: boolean; + isolatedBookings: void; +}; + +export const test = base.extend({ + environment: [ + async ({ browser }, use) => { + const environment = await compose(browser); + try { + await use(environment); + } finally { + await environment.stop(); + } + }, + { scope: "worker", timeout: 480_000 }, + ], + role: ["member", { option: true }], + authenticate: [true, { option: true }], + baseURL: async ({ environment }, use) => { + await use(environment.appUrl); + }, + isolatedBookings: [ + async ({ environment }, use, testInfo) => { + await environment.resetBookings(); + try { + await use(); + } finally { + if (testInfo.status !== testInfo.expectedStatus) { + for (const [name, log] of Object.entries(environment.logs)) { + await testInfo.attach(`${name}.log`, { body: log, contentType: "text/plain" }); + } + } + } + }, + { auto: true }, + ], + page: async ({ page, environment, role, authenticate }, use) => { + if (authenticate) await loginAs(page, environment, role); + await use(page); + }, +}); + +export { expect, loginAs }; diff --git a/e2e/permissions.spec.ts b/e2e/permissions.spec.ts new file mode 100644 index 0000000..31c2189 --- /dev/null +++ b/e2e/permissions.spec.ts @@ -0,0 +1,85 @@ +import { test, expect, loginAs } from "./fixtures"; +import { bookingDate, createBooking, graphql, openBooking } from "./booking-helpers"; +import type { + CurrentUserQuery, + DeleteBookingMutation, + CreateBookingMutation, + CreateRuleMutation, + BookingDetailQuery, +} from "../frontend/src/generated/graphql"; + +test("Gamma membership controls the UI and the API rejects a forged group booking", async ({ + page, + environment, +}) => { + await createBooking(page, "E2E protected group booking"); + const id = await openBooking(page, "E2E protected group booking"); + await loginAs(page, environment, "outsider"); + const identity = await graphql(page, "{ user { sub cid groups is_admin } }"); + expect(identity.user?.cid).toBe("bookguest"); + expect(identity.user?.groups).toEqual([]); + expect(identity.user?.is_admin).toBe(false); + + await page.goto("/new-event"); + await expect(page.getByRole("alert")).toHaveText( + "You need membership in an active group to make a booking.", + ); + await expect(page.getByRole("button", { name: "Save booking", exact: true })).toHaveCount(0); + await page.goto("/rules"); + await expect(page.getByRole("heading", { name: "Rules", exact: true })).toBeVisible(); + await expect(page.getByRole("button", { name: "New rule", exact: true })).toHaveCount(0); + await page.goto(`/bookings/${id}`); + await expect( + page.getByRole("heading", { name: "E2E protected group booking", exact: true }), + ).toBeVisible(); + await expect(page.getByRole("button", { name: "Edit", exact: true })).toHaveCount(0); + await expect(page.getByRole("button", { name: "Delete", exact: true })).toHaveCount(0); + + const deletion = await graphql( + page, + "mutation($id: String!) { deleteEvent(id: $id) { en sv } }", + { id }, + ); + expect(deletion.deleteEvent?.en).toBe("You may not delete this event"); + const date = await bookingDate(page); + const forged = await graphql( + page, + "mutation($event: InputEvent!) { createEvent(event: $event) { en sv } }", + { + event: { + title: "Forged booking", + phone: "0701234567", + start: `${date}T15:00:00.000Z`, + end: `${date}T16:00:00.000Z`, + room: ["CTC"], + booked_as: "digit", + booking_terms: true, + }, + }, + ); + expect(forged.createEvent?.en).toBe("Booking group not specified"); + const rule = await graphql( + page, + "mutation($rule: InputRule!) { createRule(rule: $rule) { en sv } }", + { + rule: { + title: "Forged rule", + description: "", + start_date: date, + end_date: date, + start_time: "12:00", + end_time: "13:00", + day_mask: 127, + room: ["BIG_HUB"], + allow: false, + }, + }, + ); + expect(rule.createRule?.en).toBe("You do not have permission to create rules"); + const unchanged = await graphql( + page, + "query($id: String!) { event(id: $id) { id title } }", + { id }, + ); + expect(unchanged.event?.title).toBe("E2E protected group booking"); +}); diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json new file mode 100644 index 0000000..a431633 --- /dev/null +++ b/e2e/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2023", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "noEmit": true, + "skipLibCheck": true, + "types": ["node"] + }, + "include": ["./**/*.ts", "../playwright.config.ts"] +} diff --git a/frontend/.prettierrc b/frontend/.prettierrc deleted file mode 100644 index 6f47b18..0000000 --- a/frontend/.prettierrc +++ /dev/null @@ -1,12 +0,0 @@ -{ - "tabWidth": 2, - "printWidth": 80, - "semi": true, - "arrowParens": "avoid", - "bracketSpacing": true, - "useTabs": false, - "trailingComma": "all", - "jsxBracketSameLine": false, - "singleQuote": false, - "jsxSingleQuote": false -} \ No newline at end of file diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 55dc48e..d6d5320 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,11 +1,16 @@ -FROM node:16 as build +# Build from the repository root: docker build -f frontend/Dockerfile . +FROM node:24.19.0-bookworm-slim AS build +ENV CI=true COREPACK_HOME=/opt/corepack +RUN corepack enable && corepack prepare pnpm@12.3.4 --activate +WORKDIR /workspace +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ +COPY frontend/package.json ./frontend/package.json +COPY backend/package.json ./backend/package.json +RUN pnpm install --frozen-lockfile --filter @bookit/frontend +COPY frontend ./frontend +RUN pnpm --dir frontend build -COPY package.json package-lock.json ./ - -RUN npm install --production -COPY . . -RUN npm run build - -FROM nginx:alpine -COPY default.conf /etc/nginx/conf.d/default.conf -COPY --from=build ./build /usr/share/nginx/html +FROM nginx:1.30.0-alpine +COPY frontend/default.conf /etc/nginx/conf.d/default.conf +COPY --from=build /workspace/frontend/dist /usr/share/nginx/html +EXPOSE 80 diff --git a/frontend/components.json b/frontend/components.json new file mode 100644 index 0000000..192d3e6 --- /dev/null +++ b/frontend/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/styles.css", + "baseColor": "neutral", + "cssVariables": true + }, + "iconLibrary": "lucide", + "registries": { "@fullcalendar": "https://shadcn-registry.fullcalendar.io/{name}.json" }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + } +} diff --git a/frontend/default.conf b/frontend/default.conf index f7ebd96..8c672b0 100644 --- a/frontend/default.conf +++ b/frontend/default.conf @@ -1,8 +1,11 @@ server { listen 80; - location ~ .* { + server_tokens off; + add_header X-Content-Type-Options nosniff always; + add_header Referrer-Policy same-origin always; + location / { root /usr/share/nginx/html; index index.html index.htm; - try_files $uri $uri/ /index.html =404; + try_files $uri $uri/ /index.html; } -} \ No newline at end of file +} diff --git a/frontend/dev_setup.sh b/frontend/dev_setup.sh index 1f5a35a..0f5bd32 100755 --- a/frontend/dev_setup.sh +++ b/frontend/dev_setup.sh @@ -1,3 +1,2 @@ -export NODE_OPTIONS=--openssl-legacy-provider - -npm i +# From the repository root: pnpm install && pnpm --dir frontend dev +# Vite+ uses modern Node directly; no legacy OpenSSL provider is needed. diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..4131eb8 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + BookIT + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json deleted file mode 100644 index b4b783f..0000000 --- a/frontend/package-lock.json +++ /dev/null @@ -1,21999 +0,0 @@ -{ - "name": "frontend", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "frontend", - "version": "0.1.0", - "dependencies": { - "@cthit/react-digit-components": "^1.0.4-alpha.2", - "@fullcalendar/core": "^5.9.0", - "@fullcalendar/daygrid": "^5.9.0", - "@fullcalendar/interaction": "^5.9.0", - "@fullcalendar/list": "^5.9.0", - "@fullcalendar/moment": "^5.9.0", - "@fullcalendar/react": "^5.9.0", - "@fullcalendar/timegrid": "^5.9.0", - "@material-ui/icons": "^4.11.2", - "axios": "^0.21.1", - "moment": "^2.29.1", - "prop-types": "^15.7.2", - "react": "^17.0.1", - "react-dom": "^17.0.1", - "react-router-dom": "^5.2.0", - "react-scripts": "4.0.3", - "typescript": "^4.9.5", - "web-vitals": "^1.0.1" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", - "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dependencies": { - "@babel/highlight": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.0.tgz", - "integrity": "sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.17.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz", - "integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==", - "dependencies": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.3", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helpers": "^7.17.2", - "@babel/parser": "^7.17.3", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.3", - "@babel/types": "^7.17.0", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", - "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==", - "dependencies": { - "@babel/types": "^7.17.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", - "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz", - "integrity": "sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==", - "dependencies": { - "@babel/helper-explode-assignable-expression": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", - "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==", - "dependencies": { - "@babel/compat-data": "^7.16.4", - "@babel/helper-validator-option": "^7.16.7", - "browserslist": "^4.17.5", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.17.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.6.tgz", - "integrity": "sha512-SogLLSxXm2OkBbSsHZMM4tUi8fUzjs63AT/d0YQIzr6GSd8Hxsbk2KYDX0k0DweAzGMj/YWeiCsorIdtdcW8Eg==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-member-expression-to-functions": "^7.16.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.0.tgz", - "integrity": "sha512-awO2So99wG6KnlE+TPs6rn83gCz5WlEePJDTnLEqbchMVrBeAujURVphRdigsk094VhvZehFoNOihSlcBjwsXA==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "regexpu-core": "^5.0.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", - "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==", - "dependencies": { - "@babel/helper-compilation-targets": "^7.13.0", - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/traverse": "^7.13.0", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0-0" - } - }, - "node_modules/@babel/helper-define-polyfill-provider/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", - "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-explode-assignable-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz", - "integrity": "sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==", - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", - "dependencies": { - "@babel/helper-get-function-arity": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.7.tgz", - "integrity": "sha512-VtJ/65tYiU/6AbMTDwyoXGPKHgTsfRarivm+YbB5uAzKUyuPjgZSgAFeG87FCigc7KNHu2Pegh1XIT3lXjvz3Q==", - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", - "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.17.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.6.tgz", - "integrity": "sha512-2ULmRdqoOMpdvkbT8jONrZML/XALfzxlb052bldftkicAUy8AxSCkD5trDPQcwHNmolcl7wP6ehNqMlyUw6AaA==", - "dependencies": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/helper-validator-identifier": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.3", - "@babel/types": "^7.17.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", - "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz", - "integrity": "sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-wrap-function": "^7.16.8", - "@babel/types": "^7.16.8" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", - "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==", - "dependencies": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-member-expression-to-functions": "^7.16.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", - "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==", - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", - "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==", - "dependencies": { - "@babel/types": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", - "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz", - "integrity": "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==", - "dependencies": { - "@babel/helper-function-name": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.8", - "@babel/types": "^7.16.8" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.17.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz", - "integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==", - "dependencies": { - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.0", - "@babel/types": "^7.17.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", - "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", - "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz", - "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==", - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz", - "integrity": "sha512-anv/DObl7waiGEnC24O9zqL0pSuI9hljihqiDuFHC8d7/bjr/4RLGPWuc8rYOff/QPzbEPSkzG8wGG9aDuhHRg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.7.tgz", - "integrity": "sha512-di8vUHRdf+4aJ7ltXhaDbPoszdkh59AQtJM5soLsuHpQJdFQZOA4uGj0V2u/CZ8bJ/u8ULDL5yq6FO/bCXnKHw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", - "@babel/plugin-proposal-optional-chaining": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz", - "integrity": "sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-remap-async-to-generator": "^7.16.8", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz", - "integrity": "sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-class-static-block": { - "version": "7.17.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.17.6.tgz", - "integrity": "sha512-X/tididvL2zbs7jZCeeRJ8167U/+Ac135AM6jCAx6gYXDUviZV5Ku9UDvWS2NCuWlFjIRXklYhwo6HhAC7ETnA==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.17.6", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-proposal-decorators": { - "version": "7.17.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.17.2.tgz", - "integrity": "sha512-WH8Z95CwTq/W8rFbMqb9p3hicpt4RX4f0K659ax2VHxgOyT6qQmUaEVEjIh4WR9Eh9NymkVn5vwsrE68fAQNUw==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.17.1", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7", - "@babel/plugin-syntax-decorators": "^7.17.0", - "charcodes": "^0.2.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz", - "integrity": "sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-export-namespace-from": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz", - "integrity": "sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-json-strings": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.7.tgz", - "integrity": "sha512-lNZ3EEggsGY78JavgbHsK9u5P3pQaW7k4axlgFLYkMd7UBsiNahCITShLjNQschPyjtO6dADrL24757IdhBrsQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-json-strings": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz", - "integrity": "sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz", - "integrity": "sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz", - "integrity": "sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.17.3.tgz", - "integrity": "sha512-yuL5iQA/TbZn+RGAfxQXfi7CNLmKi1f8zInn4IgobuCWcAb7i+zj4TYzQ9l8cEzVyJ89PDGuqxK1xZpUDISesw==", - "dependencies": { - "@babel/compat-data": "^7.17.0", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz", - "integrity": "sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz", - "integrity": "sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.16.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz", - "integrity": "sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.16.10", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz", - "integrity": "sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.7.tgz", - "integrity": "sha512-QRK0YI/40VLhNVGIjRNAAQkEHws0cswSdFFjpFyt943YmJIU1da9uW63Iu6NFV6CxTZW5eTDCrwZUstBWgp/Rg==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-decorators": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.17.0.tgz", - "integrity": "sha512-qWe85yCXsvDEluNP0OyeQjH63DlhAR3W7K9BxxU1MvbDb48tgBG+Ao6IJJ6smPDrrVzSQZrbF6donpkFBMcs3A==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-flow": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.16.7.tgz", - "integrity": "sha512-UDo3YGQO0jH6ytzVwgSLv9i/CzMcUjbKenL67dTrAZPPv6GFAtDhe6jqnvmoKzC/7htNTohhos+onPtDMqJwaQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz", - "integrity": "sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz", - "integrity": "sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz", - "integrity": "sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz", - "integrity": "sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg==", - "dependencies": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-remap-async-to-generator": "^7.16.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz", - "integrity": "sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz", - "integrity": "sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz", - "integrity": "sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz", - "integrity": "sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.3.tgz", - "integrity": "sha512-dDFzegDYKlPqa72xIlbmSkly5MluLoaC1JswABGktyt6NTXSBcUuse/kWE/wvKFWJHPETpi158qJZFS3JmykJg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz", - "integrity": "sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.7.tgz", - "integrity": "sha512-03DvpbRfvWIXyK0/6QiR1KMTWeT6OcQ7tbhjrXyFS02kjuX/mu5Bvnh5SDSWHxyawit2g5aWhKwI86EE7GUnTw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz", - "integrity": "sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==", - "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-flow-strip-types": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.16.7.tgz", - "integrity": "sha512-mzmCq3cNsDpZZu9FADYYyfZJIOrSONmHcop2XEKPdBNMa4PDC4eEvcOvzZaCNcjKu72v0XQlA5y1g58aLRXdYg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-flow": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz", - "integrity": "sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz", - "integrity": "sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA==", - "dependencies": { - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz", - "integrity": "sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz", - "integrity": "sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.7.tgz", - "integrity": "sha512-KaaEtgBL7FKYwjJ/teH63oAmE3lP34N3kshz8mm4VMAw7U3PxjVwwUmxEFksbgsNUaO3wId9R2AVQYSEGRa2+g==", - "dependencies": { - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz", - "integrity": "sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==", - "dependencies": { - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.16.7.tgz", - "integrity": "sha512-DuK5E3k+QQmnOqBR9UkusByy5WZWGRxfzV529s9nPra1GE7olmxfqO2FHobEOYSPIjPBTr4p66YDcjQnt8cBmw==", - "dependencies": { - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-validator-identifier": "^7.16.7", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.7.tgz", - "integrity": "sha512-EMh7uolsC8O4xhudF2F6wedbSHm1HHZ0C6aJ7K67zcDNidMzVcxWdGr+htW9n21klm+bOn+Rx4CBsAntZd3rEQ==", - "dependencies": { - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.8.tgz", - "integrity": "sha512-j3Jw+n5PvpmhRR+mrgIh04puSANCk/T/UA3m3P1MjJkhlK906+ApHhDIqBQDdOgL/r1UYpz4GNclTXxyZrYGSw==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.7.tgz", - "integrity": "sha512-xiLDzWNMfKoGOpc6t3U+etCE2yRnn3SM09BXqWPIZOBpL2gvVrBWUKnsJx0K/ADi5F5YC5f8APFfWrz25TdlGg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz", - "integrity": "sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz", - "integrity": "sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz", - "integrity": "sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-constant-elements": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.14.5.tgz", - "integrity": "sha512-NBqLEx1GxllIOXJInJAQbrnwwYJsV3WaMHIcOwD8rhYS0AabTWn7kHdHgPgu5RmHLU0q4DMxhAMu8ue/KampgQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.16.7.tgz", - "integrity": "sha512-qgIg8BcZgd0G/Cz916D5+9kqX0c7nPZyXaP8R2tLNN5tkyIZdG5fEwBrxwplzSnjC1jvQmyMNVwUCZPcbGY7Pg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.3.tgz", - "integrity": "sha512-9tjBm4O07f7mzKSIlEmPdiE6ub7kfIe6Cd+w+oQebpATfTQMAgW+YOuWxogbKVTulA+MEO7byMeIUtQ1z+z+ZQ==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-jsx": "^7.16.7", - "@babel/types": "^7.17.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.16.7.tgz", - "integrity": "sha512-RMvQWvpla+xy6MlBpPlrKZCMRs2AGiHOGHY3xRwl0pEeim348dDyxeH4xBsMPbIMhujeq7ihE702eM2Ew0Wo+A==", - "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.16.7.tgz", - "integrity": "sha512-hs71ToC97k3QWxswh2ElzMFABXHvGiJ01IB1TbYQDGeWRKWz/MPUTh5jGExdHvosYKpnJW5Pm3S4+TA3FyX+GA==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz", - "integrity": "sha512-mF7jOgGYCkSJagJ6XCujSQg+6xC1M77/03K2oBmVJWoFGNUtnVJO4WHKJk3dnPC8HCcj4xBQP1Egm8DWh3Pb3Q==", - "dependencies": { - "regenerator-transform": "^0.14.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.7.tgz", - "integrity": "sha512-KQzzDnZ9hWQBjwi5lpY5v9shmm6IVG0U9pB18zvMu2i4H90xpT4gmqwPYsn8rObiadYe2M0gmgsiOIF5A/2rtg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.17.0.tgz", - "integrity": "sha512-fr7zPWnKXNc1xoHfrIU9mN/4XKX4VLZ45Q+oMhfsYIaHvg7mHgmhfOy/ckRWqDK7XF3QDigRpkh5DKq6+clE8A==", - "dependencies": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "babel-plugin-polyfill-corejs2": "^0.3.0", - "babel-plugin-polyfill-corejs3": "^0.5.0", - "babel-plugin-polyfill-regenerator": "^0.3.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz", - "integrity": "sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz", - "integrity": "sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz", - "integrity": "sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz", - "integrity": "sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz", - "integrity": "sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.8.tgz", - "integrity": "sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-typescript": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz", - "integrity": "sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz", - "integrity": "sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.16.11", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.16.11.tgz", - "integrity": "sha512-qcmWG8R7ZW6WBRPZK//y+E3Cli151B20W1Rv7ln27vuPaXU/8TKms6jFdiJtF7UDTxcrb7mZd88tAeK9LjdT8g==", - "dependencies": { - "@babel/compat-data": "^7.16.8", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-validator-option": "^7.16.7", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.16.7", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.16.7", - "@babel/plugin-proposal-async-generator-functions": "^7.16.8", - "@babel/plugin-proposal-class-properties": "^7.16.7", - "@babel/plugin-proposal-class-static-block": "^7.16.7", - "@babel/plugin-proposal-dynamic-import": "^7.16.7", - "@babel/plugin-proposal-export-namespace-from": "^7.16.7", - "@babel/plugin-proposal-json-strings": "^7.16.7", - "@babel/plugin-proposal-logical-assignment-operators": "^7.16.7", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7", - "@babel/plugin-proposal-numeric-separator": "^7.16.7", - "@babel/plugin-proposal-object-rest-spread": "^7.16.7", - "@babel/plugin-proposal-optional-catch-binding": "^7.16.7", - "@babel/plugin-proposal-optional-chaining": "^7.16.7", - "@babel/plugin-proposal-private-methods": "^7.16.11", - "@babel/plugin-proposal-private-property-in-object": "^7.16.7", - "@babel/plugin-proposal-unicode-property-regex": "^7.16.7", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.16.7", - "@babel/plugin-transform-async-to-generator": "^7.16.8", - "@babel/plugin-transform-block-scoped-functions": "^7.16.7", - "@babel/plugin-transform-block-scoping": "^7.16.7", - "@babel/plugin-transform-classes": "^7.16.7", - "@babel/plugin-transform-computed-properties": "^7.16.7", - "@babel/plugin-transform-destructuring": "^7.16.7", - "@babel/plugin-transform-dotall-regex": "^7.16.7", - "@babel/plugin-transform-duplicate-keys": "^7.16.7", - "@babel/plugin-transform-exponentiation-operator": "^7.16.7", - "@babel/plugin-transform-for-of": "^7.16.7", - "@babel/plugin-transform-function-name": "^7.16.7", - "@babel/plugin-transform-literals": "^7.16.7", - "@babel/plugin-transform-member-expression-literals": "^7.16.7", - "@babel/plugin-transform-modules-amd": "^7.16.7", - "@babel/plugin-transform-modules-commonjs": "^7.16.8", - "@babel/plugin-transform-modules-systemjs": "^7.16.7", - "@babel/plugin-transform-modules-umd": "^7.16.7", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.16.8", - "@babel/plugin-transform-new-target": "^7.16.7", - "@babel/plugin-transform-object-super": "^7.16.7", - "@babel/plugin-transform-parameters": "^7.16.7", - "@babel/plugin-transform-property-literals": "^7.16.7", - "@babel/plugin-transform-regenerator": "^7.16.7", - "@babel/plugin-transform-reserved-words": "^7.16.7", - "@babel/plugin-transform-shorthand-properties": "^7.16.7", - "@babel/plugin-transform-spread": "^7.16.7", - "@babel/plugin-transform-sticky-regex": "^7.16.7", - "@babel/plugin-transform-template-literals": "^7.16.7", - "@babel/plugin-transform-typeof-symbol": "^7.16.7", - "@babel/plugin-transform-unicode-escapes": "^7.16.7", - "@babel/plugin-transform-unicode-regex": "^7.16.7", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.16.8", - "babel-plugin-polyfill-corejs2": "^0.3.0", - "babel-plugin-polyfill-corejs3": "^0.5.0", - "babel-plugin-polyfill-regenerator": "^0.3.0", - "core-js-compat": "^3.20.2", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-react": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.16.7.tgz", - "integrity": "sha512-fWpyI8UM/HE6DfPBzD8LnhQ/OcH8AgTaqcqP2nGOXEUV+VKBR5JRN9hCk9ai+zQQ57vtm9oWeXguBCPNUjytgA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-validator-option": "^7.16.7", - "@babel/plugin-transform-react-display-name": "^7.16.7", - "@babel/plugin-transform-react-jsx": "^7.16.7", - "@babel/plugin-transform-react-jsx-development": "^7.16.7", - "@babel/plugin-transform-react-pure-annotations": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.16.7.tgz", - "integrity": "sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-validator-option": "^7.16.7", - "@babel/plugin-transform-typescript": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.17.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", - "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", - "dependencies": { - "regenerator-runtime": "^0.13.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/runtime-corejs3": { - "version": "7.17.2", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.17.2.tgz", - "integrity": "sha512-NcKtr2epxfIrNM4VOmPKO46TvDMCBhgi2CrSHaEarrz+Plk2K5r9QemmOFTGpZaoKnWoGH5MO+CzeRsih/Fcgg==", - "dependencies": { - "core-js-pure": "^3.20.2", - "regenerator-runtime": "^0.13.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", - "dependencies": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", - "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", - "dependencies": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.3", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.17.3", - "@babel/types": "^7.17.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", - "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", - "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==" - }, - "node_modules/@cnakazawa/watch": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", - "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", - "dependencies": { - "exec-sh": "^0.3.2", - "minimist": "^1.2.0" - }, - "bin": { - "watch": "cli.js" - }, - "engines": { - "node": ">=0.1.95" - } - }, - "node_modules/@csstools/convert-colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", - "integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/@csstools/normalize.css": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz", - "integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg==" - }, - "node_modules/@cthit/react-digit-components": { - "version": "1.0.4-alpha.2", - "resolved": "https://registry.npmjs.org/@cthit/react-digit-components/-/react-digit-components-1.0.4-alpha.2.tgz", - "integrity": "sha512-LKf10b9VIMMDdt3OEDMctePAW2X3V/KnqhnZOxXcPRxfiVS5eTouRtFAFLzokWaoGndXOTyYrw13MAkOlHWIFA==", - "dependencies": { - "@date-io/date-fns": "1.3.13", - "@material-ui/core": "4.11.3", - "@material-ui/icons": "4.11.2", - "@material-ui/lab": "4.0.0-alpha.57", - "@material-ui/pickers": "3.2.10", - "@material-ui/styles": "4.11.3", - "date-fns": "2.19.0", - "lodash": "4.17.21", - "prop-types": "15.7.2", - "react-is": "17.0.1", - "react-markdown": "5.0.3", - "yup": "0.32.9" - }, - "engines": { - "node": ">=8", - "npm": ">=5" - }, - "peerDependencies": { - "axios": "0.21.1", - "react": "17.0.1", - "react-dom": "17.0.1", - "react-router-dom": "5.2.0", - "styled-components": "5.2.1" - } - }, - "node_modules/@cthit/react-digit-components/node_modules/prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - }, - "node_modules/@cthit/react-digit-components/node_modules/prop-types/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/@cthit/react-digit-components/node_modules/react-is": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", - "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==" - }, - "node_modules/@date-io/core": { - "version": "1.3.13", - "resolved": "https://registry.npmjs.org/@date-io/core/-/core-1.3.13.tgz", - "integrity": "sha512-AlEKV7TxjeK+jxWVKcCFrfYAk8spX9aCyiToFIiLPtfQbsjmRGLIhb5VZgptQcJdHtLXo7+m0DuurwFgUToQuA==" - }, - "node_modules/@date-io/date-fns": { - "version": "1.3.13", - "resolved": "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-1.3.13.tgz", - "integrity": "sha512-yXxGzcRUPcogiMj58wVgFjc9qUYrCnnU9eLcyNbsQCmae4jPuZCDoIBR21j8ZURsM7GRtU62VOw5yNd4dDHunA==", - "dependencies": { - "@date-io/core": "^1.3.13" - }, - "peerDependencies": { - "date-fns": "^2.0.0" - } - }, - "node_modules/@emotion/hash": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", - "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" - }, - "node_modules/@emotion/is-prop-valid": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", - "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", - "peer": true, - "dependencies": { - "@emotion/memoize": "0.7.4" - } - }, - "node_modules/@emotion/memoize": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", - "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", - "peer": true - }, - "node_modules/@emotion/stylis": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", - "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==", - "peer": true - }, - "node_modules/@emotion/unitless": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", - "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", - "peer": true - }, - "node_modules/@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.12.1", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", - "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@fullcalendar/common": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@fullcalendar/common/-/common-5.9.0.tgz", - "integrity": "sha512-wfUDRg53gzUU7Ng1wqjvvM2ncFx+5PiyCq5k2KjEbMourv7m4Zw6B3xlhpQbfUv3WOnbJgwLLNLFEMcsEJpXBQ==", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@fullcalendar/core": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-5.9.0.tgz", - "integrity": "sha512-3+zSm8ykXqM2vEGYp4P/BEoR2TB0W9mN6zBGZ6SY9y0nC+Fc0+ZXJmFlYmrEYSdO4kmREnlxntkp1+o9mu8PIQ==", - "dependencies": { - "@fullcalendar/common": "~5.9.0", - "preact": "^10.0.5", - "tslib": "^2.1.0" - } - }, - "node_modules/@fullcalendar/daygrid": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-5.9.0.tgz", - "integrity": "sha512-yTYjTM+bjTWfq5uUnSMBH4j1il+b0IAAe+q2UcAeky4iggwn9pi+taUtETtlPTQCSBSWaF8TuEZ3+apB0RrjyQ==", - "dependencies": { - "@fullcalendar/common": "~5.9.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@fullcalendar/interaction": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-5.9.0.tgz", - "integrity": "sha512-bBvcY3Mh2z4rP5AqB8dL76SljPEVCVawNmdQlcKShedhxvkkMR1zMcJ3sgabLBvXqooC9f+PbmEb5LlSGEse3A==", - "dependencies": { - "@fullcalendar/common": "~5.9.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@fullcalendar/list": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-5.9.0.tgz", - "integrity": "sha512-bpekkh0RNitVgX33Y3dsorcUEs2E+KJ5qQaH26FNwYShVi1RmqQ7CDNVqIXKiAJwBWMjNJv8xHayTv01XYbmTA==", - "dependencies": { - "@fullcalendar/common": "~5.9.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@fullcalendar/moment": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@fullcalendar/moment/-/moment-5.9.0.tgz", - "integrity": "sha512-kfB5X5NTrWL0cDeFH8NWiLHDahnz4528mbaoxHw+UZGDYE6YDVtp6v0IO69RfJG6W2oNLCwX8cWV5Wkq7i9Aig==", - "dependencies": { - "@fullcalendar/common": "~5.9.0", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "moment": "^2.29.1" - } - }, - "node_modules/@fullcalendar/react": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@fullcalendar/react/-/react-5.9.0.tgz", - "integrity": "sha512-Di4V5RI0MSVEa4L9yues9f6VfAmuzZAr3EAaGzlA27GhDv+ofWoYoohPcAanRrExiCf2W4R2ZyecJh+/TRPshg==", - "dependencies": { - "@fullcalendar/common": "~5.9.0", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "react": "^16.7.0 || ^17", - "react-dom": "^16.7.0 || ^17" - } - }, - "node_modules/@fullcalendar/timegrid": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-5.9.0.tgz", - "integrity": "sha512-X9JLepHz6hSuiziVkIlgBkx/W/5E4nIqJ6i0LISk3zPFH/Arn0QjQU345ISys8OTwezPUgJvLGrl4Yy4/gRULw==", - "dependencies": { - "@fullcalendar/common": "~5.9.0", - "@fullcalendar/daygrid": "~5.9.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==" - }, - "node_modules/@hapi/address": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", - "integrity": "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==", - "deprecated": "Moved to 'npm install @sideway/address'" - }, - "node_modules/@hapi/bourne": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-1.3.2.tgz", - "integrity": "sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA==", - "deprecated": "This version has been deprecated and is no longer supported or maintained" - }, - "node_modules/@hapi/hoek": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz", - "integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==", - "deprecated": "This version has been deprecated and is no longer supported or maintained" - }, - "node_modules/@hapi/joi": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-15.1.1.tgz", - "integrity": "sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ==", - "deprecated": "Switch to 'npm install joi'", - "dependencies": { - "@hapi/address": "2.x.x", - "@hapi/bourne": "1.x.x", - "@hapi/hoek": "8.x.x", - "@hapi/topo": "3.x.x" - } - }, - "node_modules/@hapi/topo": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.6.tgz", - "integrity": "sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ==", - "deprecated": "This version has been deprecated and is no longer supported or maintained", - "dependencies": { - "@hapi/hoek": "^8.3.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" - }, - "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==", - "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/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "engines": { - "node": ">=6" - } - }, - "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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz", - "integrity": "sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g==", - "dependencies": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^26.6.2", - "jest-util": "^26.6.2", - "slash": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/console/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/console/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/console/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/console/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/console/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.6.3.tgz", - "integrity": "sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw==", - "dependencies": { - "@jest/console": "^26.6.2", - "@jest/reporters": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^26.6.2", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-resolve-dependencies": "^26.6.3", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "jest-watcher": "^26.6.2", - "micromatch": "^4.0.2", - "p-each-series": "^2.1.0", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/core/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/core/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/core/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core/node_modules/jest-resolve": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", - "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", - "dependencies": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^26.6.2", - "read-pkg-up": "^7.0.1", - "resolve": "^1.18.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/core/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/environment": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz", - "integrity": "sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA==", - "dependencies": { - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/fake-timers": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz", - "integrity": "sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==", - "dependencies": { - "@jest/types": "^26.6.2", - "@sinonjs/fake-timers": "^6.0.1", - "@types/node": "*", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/globals": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz", - "integrity": "sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA==", - "dependencies": { - "@jest/environment": "^26.6.2", - "@jest/types": "^26.6.2", - "expect": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/reporters": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.6.2.tgz", - "integrity": "sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw==", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.4", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.3", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "jest-haste-map": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^7.0.0" - }, - "engines": { - "node": ">= 10.14.2" - }, - "optionalDependencies": { - "node-notifier": "^8.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/reporters/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dependencies": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/reporters/node_modules/jest-resolve": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", - "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", - "dependencies": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^26.6.2", - "read-pkg-up": "^7.0.1", - "resolve": "^1.18.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/reporters/node_modules/jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/@jest/reporters/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@jest/reporters/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@jest/reporters/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/source-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz", - "integrity": "sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA==", - "dependencies": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", - "source-map": "^0.6.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/source-map/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@jest/test-result": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz", - "integrity": "sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ==", - "dependencies": { - "@jest/console": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz", - "integrity": "sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==", - "dependencies": { - "@jest/test-result": "^26.6.2", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/transform": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz", - "integrity": "sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==", - "dependencies": { - "@babel/core": "^7.1.0", - "@jest/types": "^26.6.2", - "babel-plugin-istanbul": "^6.0.0", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-util": "^26.6.2", - "micromatch": "^4.0.2", - "pirates": "^4.0.1", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/transform/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/transform/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/transform/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/transform/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@jest/transform/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/types/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/types/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/types/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", - "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", - "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@material-ui/core": { - "version": "4.11.3", - "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.11.3.tgz", - "integrity": "sha512-Adt40rGW6Uds+cAyk3pVgcErpzU/qxc7KBR94jFHBYretU4AtWZltYcNsbeMn9tXL86jjVL1kuGcIHsgLgFGRw==", - "deprecated": "You can now upgrade to @mui/material. See the guide: https://mui.com/guides/migration-v4/", - "dependencies": { - "@babel/runtime": "^7.4.4", - "@material-ui/styles": "^4.11.3", - "@material-ui/system": "^4.11.3", - "@material-ui/types": "^5.1.0", - "@material-ui/utils": "^4.11.2", - "@types/react-transition-group": "^4.2.0", - "clsx": "^1.0.4", - "hoist-non-react-statics": "^3.3.2", - "popper.js": "1.16.1-lts", - "prop-types": "^15.7.2", - "react-is": "^16.8.0 || ^17.0.0", - "react-transition-group": "^4.4.0" - }, - "engines": { - "node": ">=8.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/material-ui" - }, - "peerDependencies": { - "@types/react": "^16.8.6 || ^17.0.0", - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@material-ui/icons": { - "version": "4.11.2", - "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.2.tgz", - "integrity": "sha512-fQNsKX2TxBmqIGJCSi3tGTO/gZ+eJgWmMJkgDiOfyNaunNaxcklJQFaFogYcFl0qFuaEz1qaXYXboa/bUXVSOQ==", - "dependencies": { - "@babel/runtime": "^7.4.4" - }, - "engines": { - "node": ">=8.0.0" - }, - "peerDependencies": { - "@material-ui/core": "^4.0.0", - "@types/react": "^16.8.6 || ^17.0.0", - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@material-ui/lab": { - "version": "4.0.0-alpha.57", - "resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.57.tgz", - "integrity": "sha512-qo/IuIQOmEKtzmRD2E4Aa6DB4A87kmY6h0uYhjUmrrgmEAgbbw9etXpWPVXuRK6AGIQCjFzV6WO2i21m1R4FCw==", - "deprecated": "You can now upgrade to @mui/lab. See the guide: https://mui.com/guides/migration-v4/", - "dependencies": { - "@babel/runtime": "^7.4.4", - "@material-ui/utils": "^4.11.2", - "clsx": "^1.0.4", - "prop-types": "^15.7.2", - "react-is": "^16.8.0 || ^17.0.0" - }, - "engines": { - "node": ">=8.0.0" - }, - "peerDependencies": { - "@material-ui/core": "^4.9.10", - "@types/react": "^16.8.6 || ^17.0.0", - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@material-ui/pickers": { - "version": "3.2.10", - "resolved": "https://registry.npmjs.org/@material-ui/pickers/-/pickers-3.2.10.tgz", - "integrity": "sha512-B8G6Obn5S3RCl7hwahkQj9sKUapwXWFjiaz/Bsw1fhYFdNMnDUolRiWQSoKPb1/oKe37Dtfszoywi1Ynbo3y8w==", - "dependencies": { - "@babel/runtime": "^7.6.0", - "@date-io/core": "1.x", - "@types/styled-jsx": "^2.2.8", - "clsx": "^1.0.2", - "react-transition-group": "^4.0.0", - "rifm": "^0.7.0" - }, - "peerDependencies": { - "@date-io/core": "^1.3.6", - "@material-ui/core": "^4.0.0", - "prop-types": "^15.6.0", - "react": "^16.8.4", - "react-dom": "^16.8.4" - } - }, - "node_modules/@material-ui/styles": { - "version": "4.11.3", - "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.3.tgz", - "integrity": "sha512-HzVzCG+PpgUGMUYEJ2rTEmQYeonGh41BYfILNFb/1ueqma+p1meSdu4RX6NjxYBMhf7k+jgfHFTTz+L1SXL/Zg==", - "deprecated": "You can now upgrade to @mui/styles. See the guide: https://mui.com/guides/migration-v4/", - "dependencies": { - "@babel/runtime": "^7.4.4", - "@emotion/hash": "^0.8.0", - "@material-ui/types": "^5.1.0", - "@material-ui/utils": "^4.11.2", - "clsx": "^1.0.4", - "csstype": "^2.5.2", - "hoist-non-react-statics": "^3.3.2", - "jss": "^10.5.1", - "jss-plugin-camel-case": "^10.5.1", - "jss-plugin-default-unit": "^10.5.1", - "jss-plugin-global": "^10.5.1", - "jss-plugin-nested": "^10.5.1", - "jss-plugin-props-sort": "^10.5.1", - "jss-plugin-rule-value-function": "^10.5.1", - "jss-plugin-vendor-prefixer": "^10.5.1", - "prop-types": "^15.7.2" - }, - "engines": { - "node": ">=8.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/material-ui" - }, - "peerDependencies": { - "@types/react": "^16.8.6 || ^17.0.0", - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@material-ui/system": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.12.1.tgz", - "integrity": "sha512-lUdzs4q9kEXZGhbN7BptyiS1rLNHe6kG9o8Y307HCvF4sQxbCgpL2qi+gUk+yI8a2DNk48gISEQxoxpgph0xIw==", - "deprecated": "You can now upgrade to @mui/system. See the guide: https://mui.com/guides/migration-v4/", - "dependencies": { - "@babel/runtime": "^7.4.4", - "@material-ui/utils": "^4.11.2", - "csstype": "^2.5.2", - "prop-types": "^15.7.2" - }, - "engines": { - "node": ">=8.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/material-ui" - }, - "peerDependencies": { - "@types/react": "^16.8.6 || ^17.0.0", - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@material-ui/types": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz", - "integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==", - "peerDependencies": { - "@types/react": "*" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@material-ui/utils": { - "version": "4.11.2", - "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.11.2.tgz", - "integrity": "sha512-Uul8w38u+PICe2Fg2pDKCaIG7kOyhowZ9vjiC1FsVwPABTW8vPPKfF6OvxRq3IiBaI1faOJmgdvMG7rMJARBhA==", - "dependencies": { - "@babel/runtime": "^7.4.4", - "prop-types": "^15.7.2", - "react-is": "^16.8.0 || ^17.0.0" - }, - "engines": { - "node": ">=8.0.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@npmcli/fs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", - "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", - "dependencies": { - "@gar/promisify": "^1.0.1", - "semver": "^7.3.5" - } - }, - "node_modules/@npmcli/fs/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@npmcli/move-file": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", - "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", - "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@npmcli/move-file/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@pmmmwh/react-refresh-webpack-plugin": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.4.3.tgz", - "integrity": "sha512-br5Qwvh8D2OQqSXpd1g/xqXKnK0r+Jz6qVKBbWmpUcrbGOxUrf39V5oZ1876084CGn18uMdR5uvPqBv9UqtBjQ==", - "dependencies": { - "ansi-html": "^0.0.7", - "error-stack-parser": "^2.0.6", - "html-entities": "^1.2.1", - "native-url": "^0.2.6", - "schema-utils": "^2.6.5", - "source-map": "^0.7.3" - }, - "engines": { - "node": ">= 10.x" - }, - "peerDependencies": { - "@types/webpack": "4.x", - "react-refresh": ">=0.8.3 <0.10.0", - "sockjs-client": "^1.4.0", - "type-fest": "^0.13.1", - "webpack": ">=4.43.0 <6.0.0", - "webpack-dev-server": "3.x", - "webpack-hot-middleware": "2.x", - "webpack-plugin-serve": "0.x || 1.x" - }, - "peerDependenciesMeta": { - "@types/webpack": { - "optional": true - }, - "sockjs-client": { - "optional": true - }, - "type-fest": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - }, - "webpack-hot-middleware": { - "optional": true - }, - "webpack-plugin-serve": { - "optional": true - } - } - }, - "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz", - "integrity": "sha512-RxtSL3XmdTAE2byxekYLnx+98kEUOrPHF/KRVjLH+DEIHy6kjIw7YINQzn+NXiH/NTrQLAwYs0GWB+csWygA9Q==", - "dependencies": { - "@rollup/pluginutils": "^3.0.8", - "@types/resolve": "0.0.8", - "builtin-modules": "^3.1.0", - "is-module": "^1.0.0", - "resolve": "^1.14.2" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@rollup/plugin-replace": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", - "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", - "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "magic-string": "^0.25.7" - }, - "peerDependencies": { - "rollup": "^1.20.0 || ^2.0.0" - } - }, - "node_modules/@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "dependencies": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@rollup/pluginutils/node_modules/@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/@surma/rollup-plugin-off-main-thread": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-1.4.2.tgz", - "integrity": "sha512-yBMPqmd1yEJo/280PAMkychuaALyQ9Lkb5q1ck3mjJrFuEobIfhnQ4J3mbvBoISmR3SWMWV+cGB/I0lCQee79A==", - "dependencies": { - "ejs": "^2.6.1", - "magic-string": "^0.25.0" - } - }, - "node_modules/@svgr/babel-plugin-add-jsx-attribute": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz", - "integrity": "sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz", - "integrity": "sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz", - "integrity": "sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz", - "integrity": "sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/babel-plugin-svg-dynamic-title": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz", - "integrity": "sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg==", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/babel-plugin-svg-em-dimensions": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz", - "integrity": "sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw==", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/babel-plugin-transform-react-native-svg": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz", - "integrity": "sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q==", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/babel-plugin-transform-svg-component": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz", - "integrity": "sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/babel-preset": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.5.0.tgz", - "integrity": "sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig==", - "dependencies": { - "@svgr/babel-plugin-add-jsx-attribute": "^5.4.0", - "@svgr/babel-plugin-remove-jsx-attribute": "^5.4.0", - "@svgr/babel-plugin-remove-jsx-empty-expression": "^5.0.1", - "@svgr/babel-plugin-replace-jsx-attribute-value": "^5.0.1", - "@svgr/babel-plugin-svg-dynamic-title": "^5.4.0", - "@svgr/babel-plugin-svg-em-dimensions": "^5.4.0", - "@svgr/babel-plugin-transform-react-native-svg": "^5.4.0", - "@svgr/babel-plugin-transform-svg-component": "^5.5.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/core": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/core/-/core-5.5.0.tgz", - "integrity": "sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ==", - "dependencies": { - "@svgr/plugin-jsx": "^5.5.0", - "camelcase": "^6.2.0", - "cosmiconfig": "^7.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/hast-util-to-babel-ast": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz", - "integrity": "sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ==", - "dependencies": { - "@babel/types": "^7.12.6" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/plugin-jsx": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz", - "integrity": "sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA==", - "dependencies": { - "@babel/core": "^7.12.3", - "@svgr/babel-preset": "^5.5.0", - "@svgr/hast-util-to-babel-ast": "^5.5.0", - "svg-parser": "^2.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/plugin-svgo": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz", - "integrity": "sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ==", - "dependencies": { - "cosmiconfig": "^7.0.0", - "deepmerge": "^4.2.2", - "svgo": "^1.2.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/webpack": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.5.0.tgz", - "integrity": "sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g==", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/plugin-transform-react-constant-elements": "^7.12.1", - "@babel/preset-env": "^7.12.1", - "@babel/preset-react": "^7.12.5", - "@svgr/core": "^5.5.0", - "@svgr/plugin-jsx": "^5.5.0", - "@svgr/plugin-svgo": "^5.5.0", - "loader-utils": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/@types/babel__core": { - "version": "7.1.18", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.18.tgz", - "integrity": "sha512-S7unDjm/C7z2A2R9NzfKCK1I+BAALDtxEmsJBwlB3EzNfb929ykjL++1CK9LO++EIp2fQrC8O+BwjKvz6UeDyQ==", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz", - "integrity": "sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA==", - "dependencies": { - "@babel/types": "^7.3.0" - } - }, - "node_modules/@types/eslint": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.29.0.tgz", - "integrity": "sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng==", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" - }, - "node_modules/@types/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", - "dependencies": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/html-minifier-terser": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz", - "integrity": "sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w==" - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==" - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=" - }, - "node_modules/@types/lodash": { - "version": "4.14.178", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", - "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==" - }, - "node_modules/@types/mdast": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", - "integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==" - }, - "node_modules/@types/node": { - "version": "17.0.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", - "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==" - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==" - }, - "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" - }, - "node_modules/@types/prettier": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.4.tgz", - "integrity": "sha512-ReVR2rLTV1kvtlWFyuot+d1pkpG2Fw/XKE3PDAdj57rbM97ttSp9JZ2UsP+2EHTylra9cUf6JA7tGwW1INzUrA==" - }, - "node_modules/@types/prop-types": { - "version": "15.7.4", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", - "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==" - }, - "node_modules/@types/q": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", - "integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==" - }, - "node_modules/@types/react": { - "version": "17.0.17", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.17.tgz", - "integrity": "sha512-nrfi7I13cAmrd0wje8czYpf5SFbryczCtPzFc6ijqvdjKcyA3tCvGxwchOUlxb2ucBPuJ9Y3oUqKrRqZvrz0lw==", - "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-transition-group": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz", - "integrity": "sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug==", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/react/node_modules/csstype": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz", - "integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==" - }, - "node_modules/@types/resolve": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", - "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" - }, - "node_modules/@types/source-list-map": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", - "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==" - }, - "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" - }, - "node_modules/@types/styled-jsx": { - "version": "2.2.9", - "resolved": "https://registry.npmjs.org/@types/styled-jsx/-/styled-jsx-2.2.9.tgz", - "integrity": "sha512-W/iTlIkGEyTBGTEvZCey8EgQlQ5l0DwMqi3iOXlLs2kyBwYTXHKEiU6IZ5EwoRwngL8/dGYuzezSup89ttVHLw==", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/tapable": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.8.tgz", - "integrity": "sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==" - }, - "node_modules/@types/uglify-js": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.13.1.tgz", - "integrity": "sha512-O3MmRAk6ZuAKa9CHgg0Pr0+lUOqoMLpc9AS4R8ano2auvsg7IE8syF3Xh/NPr26TWklxYcqoEEFdzLLs1fV9PQ==", - "dependencies": { - "source-map": "^0.6.1" - } - }, - "node_modules/@types/uglify-js/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@types/unist": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", - "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==" - }, - "node_modules/@types/webpack": { - "version": "4.41.32", - "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.32.tgz", - "integrity": "sha512-cb+0ioil/7oz5//7tZUSwbrSAN/NWHrQylz5cW8G0dWTcF/g+/dSdMlKVZspBYuMAN1+WnwHrkxiRrLcwd0Heg==", - "dependencies": { - "@types/node": "*", - "@types/tapable": "^1", - "@types/uglify-js": "*", - "@types/webpack-sources": "*", - "anymatch": "^3.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/@types/webpack-sources": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-3.2.0.tgz", - "integrity": "sha512-Ft7YH3lEVRQ6ls8k4Ff1oB4jN6oy/XmU6tQISKdhfh+1mR+viZFphS6WL0IrtDOzvefmJg5a0s7ZQoRXwqTEFg==", - "dependencies": { - "@types/node": "*", - "@types/source-list-map": "*", - "source-map": "^0.7.3" - } - }, - "node_modules/@types/webpack-sources/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@types/webpack/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@types/yargs": { - "version": "15.0.14", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", - "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", - "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz", - "integrity": "sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg==", - "dependencies": { - "@typescript-eslint/experimental-utils": "4.33.0", - "@typescript-eslint/scope-manager": "4.33.0", - "debug": "^4.3.1", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", - "regexpp": "^3.1.0", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^4.0.0", - "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/experimental-utils": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz", - "integrity": "sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q==", - "dependencies": { - "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.33.0", - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/typescript-estree": "4.33.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.33.0.tgz", - "integrity": "sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==", - "dependencies": { - "@typescript-eslint/scope-manager": "4.33.0", - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/typescript-estree": "4.33.0", - "debug": "^4.3.1" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz", - "integrity": "sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ==", - "dependencies": { - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/visitor-keys": "4.33.0" - }, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz", - "integrity": "sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==", - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz", - "integrity": "sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==", - "dependencies": { - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/visitor-keys": "4.33.0", - "debug": "^4.3.1", - "globby": "^11.0.3", - "is-glob": "^4.0.1", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz", - "integrity": "sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==", - "dependencies": { - "@typescript-eslint/types": "4.33.0", - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", - "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", - "dependencies": { - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", - "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", - "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", - "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==" - }, - "node_modules/@webassemblyjs/helper-code-frame": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", - "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", - "dependencies": { - "@webassemblyjs/wast-printer": "1.9.0" - } - }, - "node_modules/@webassemblyjs/helper-fsm": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", - "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==" - }, - "node_modules/@webassemblyjs/helper-module-context": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", - "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", - "dependencies": { - "@webassemblyjs/ast": "1.9.0" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", - "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==" - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", - "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", - "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", - "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", - "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==" - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", - "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/helper-wasm-section": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-opt": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "@webassemblyjs/wast-printer": "1.9.0" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", - "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", - "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", - "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" - } - }, - "node_modules/@webassemblyjs/wast-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", - "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/floating-point-hex-parser": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-code-frame": "1.9.0", - "@webassemblyjs/helper-fsm": "1.9.0", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", - "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" - }, - "node_modules/abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==" - }, - "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==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/address": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/address/-/address-1.1.2.tgz", - "integrity": "sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==", - "engines": { - "node": ">= 0.12.0" - } - }, - "node_modules/adjust-sourcemap-loader": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-3.0.0.tgz", - "integrity": "sha512-YBrGyT2/uVQ/c6Rr+t6ZJXniY03YtHGMJQYal368burRGYKqhx9qGTWqcBU5s1CwYY9E/ri63RYyG1IacMZtqw==", - "dependencies": { - "loader-utils": "^2.0.0", - "regex-parser": "^2.2.11" - }, - "engines": { - "node": ">=8.9" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-errors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "peerDependencies": { - "ajv": ">=5.0.0" - } - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/alphanum-sort": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", - "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=" - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-html": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", - "integrity": "sha512-JoAxEa1DfP9m2xfB/y2r/aKcwXNlltr4+0QSBC4TrLfcxyvepX2Pv0t/xpgGV5bGsDzCYV8SzjWgyCW0T9yYbA==", - "engines": [ - "node >= 0.8.0" - ], - "bin": { - "ansi-html": "bin/ansi-html" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", - "dependencies": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/arity-n": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/arity-n/-/arity-n-1.0.4.tgz", - "integrity": "sha1-2edrEXM+CFacCEeuezmyhgswt0U=" - }, - "node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" - }, - "node_modules/array-includes": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", - "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", - "get-intrinsic": "^1.1.1", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", - "integrity": "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flatmap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.5.tgz", - "integrity": "sha512-08u6rVyi1Lj7oqWbS9nUxliETrtIROT4XGTA4D/LWGten6E3ocm7cy9SIrmNHOL5XVbVuckUp3X6Xyg8/zpvHA==", - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "engines": { - "node": ">=8" - } - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" - }, - "node_modules/asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "dependencies": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/asn1.js/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "node_modules/assert": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", - "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", - "dependencies": { - "object-assign": "^4.1.1", - "util": "0.10.3" - } - }, - "node_modules/assert/node_modules/inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" - }, - "node_modules/assert/node_modules/util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dependencies": { - "inherits": "2.0.1" - } - }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ast-types-flow": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=" - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "dependencies": { - "lodash": "^4.17.14" - } - }, - "node_modules/async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==" - }, - "node_modules/async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/autoprefixer": { - "version": "9.8.8", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.8.tgz", - "integrity": "sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA==", - "dependencies": { - "browserslist": "^4.12.0", - "caniuse-lite": "^1.0.30001109", - "normalize-range": "^0.1.2", - "num2fraction": "^1.2.2", - "picocolors": "^0.2.1", - "postcss": "^7.0.32", - "postcss-value-parser": "^4.1.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "funding": { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - } - }, - "node_modules/autoprefixer/node_modules/picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" - }, - "node_modules/axe-core": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.1.tgz", - "integrity": "sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw==", - "engines": { - "node": ">=4" - } - }, - "node_modules/axios": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", - "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", - "dependencies": { - "follow-redirects": "^1.10.0" - } - }, - "node_modules/axobject-query": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", - "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==" - }, - "node_modules/babel-eslint": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", - "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", - "deprecated": "babel-eslint is now @babel/eslint-parser. This package will no longer receive updates.", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.0", - "@babel/traverse": "^7.7.0", - "@babel/types": "^7.7.0", - "eslint-visitor-keys": "^1.0.0", - "resolve": "^1.12.0" - }, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "eslint": ">= 4.12.1" - } - }, - "node_modules/babel-eslint/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/babel-extract-comments": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/babel-extract-comments/-/babel-extract-comments-1.0.0.tgz", - "integrity": "sha512-qWWzi4TlddohA91bFwgt6zO/J0X+io7Qp184Fw0m2JYRSTZnJbFR8+07KmzudHCZgOiKRCrjhylwv9Xd8gfhVQ==", - "dependencies": { - "babylon": "^6.18.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/babel-jest": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", - "integrity": "sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==", - "dependencies": { - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/babel__core": "^7.1.7", - "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "slash": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-jest/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/babel-jest/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/babel-jest/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-jest/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-loader": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.1.0.tgz", - "integrity": "sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw==", - "dependencies": { - "find-cache-dir": "^2.1.0", - "loader-utils": "^1.4.0", - "mkdirp": "^0.5.3", - "pify": "^4.0.1", - "schema-utils": "^2.6.5" - }, - "engines": { - "node": ">= 6.9" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "webpack": ">=2" - } - }, - "node_modules/babel-loader/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/babel-loader/node_modules/loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dependencies": { - "object.assign": "^4.1.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz", - "integrity": "sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw==", - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/babel-plugin-macros": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", - "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "dependencies": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">=10", - "npm": ">=6" - } - }, - "node_modules/babel-plugin-named-asset-import": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.8.tgz", - "integrity": "sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q==", - "peerDependencies": { - "@babel/core": "^7.1.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", - "integrity": "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==", - "dependencies": { - "@babel/compat-data": "^7.13.11", - "@babel/helper-define-polyfill-provider": "^0.3.1", - "semver": "^6.1.1" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz", - "integrity": "sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.1", - "core-js-compat": "^3.21.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", - "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-styled-components": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.6.tgz", - "integrity": "sha512-Sk+7o/oa2HfHv3Eh8sxoz75/fFvEdHsXV4grdeHufX0nauCmymlnN0rGhIvfpMQSJMvGutJ85gvCGea4iqmDpg==", - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.0", - "@babel/helper-module-imports": "^7.16.0", - "babel-plugin-syntax-jsx": "^6.18.0", - "lodash": "^4.17.11", - "picomatch": "^2.3.0" - }, - "peerDependencies": { - "styled-components": ">= 2" - } - }, - "node_modules/babel-plugin-syntax-jsx": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", - "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=", - "peer": true - }, - "node_modules/babel-plugin-syntax-object-rest-spread": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", - "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=" - }, - "node_modules/babel-plugin-transform-object-rest-spread": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz", - "integrity": "sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=", - "dependencies": { - "babel-plugin-syntax-object-rest-spread": "^6.8.0", - "babel-runtime": "^6.26.0" - } - }, - "node_modules/babel-plugin-transform-react-remove-prop-types": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", - "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==" - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz", - "integrity": "sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ==", - "dependencies": { - "babel-plugin-jest-hoist": "^26.6.2", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": ">= 10.14.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-react-app": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-10.0.1.tgz", - "integrity": "sha512-b0D9IZ1WhhCWkrTXyFuIIgqGzSkRIH5D5AmB0bXbzYAB1OBAwHcUeyWW2LorutLWF5btNo/N7r/cIdmvvKJlYg==", - "dependencies": { - "@babel/core": "^7.16.0", - "@babel/plugin-proposal-class-properties": "^7.16.0", - "@babel/plugin-proposal-decorators": "^7.16.4", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0", - "@babel/plugin-proposal-numeric-separator": "^7.16.0", - "@babel/plugin-proposal-optional-chaining": "^7.16.0", - "@babel/plugin-proposal-private-methods": "^7.16.0", - "@babel/plugin-transform-flow-strip-types": "^7.16.0", - "@babel/plugin-transform-react-display-name": "^7.16.0", - "@babel/plugin-transform-runtime": "^7.16.4", - "@babel/preset-env": "^7.16.4", - "@babel/preset-react": "^7.16.0", - "@babel/preset-typescript": "^7.16.0", - "@babel/runtime": "^7.16.3", - "babel-plugin-macros": "^3.1.0", - "babel-plugin-transform-react-remove-prop-types": "^0.4.24" - } - }, - "node_modules/babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dependencies": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "node_modules/babel-runtime/node_modules/core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "deprecated": "core-js@<3.4 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.", - "hasInstallScript": true - }, - "node_modules/babel-runtime/node_modules/regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" - }, - "node_modules/babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "bin": { - "babylon": "bin/babylon.js" - } - }, - "node_modules/bail": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", - "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "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==" - }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=" - }, - "node_modules/bfj": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/bfj/-/bfj-7.0.2.tgz", - "integrity": "sha512-+e/UqUzwmzJamNF50tBV6tZPTORow7gQ96iFow+8b562OdMpEK0BcJEq2OSPEDmAbSMBQ7PKZ87ubFkgxpYWgw==", - "dependencies": { - "bluebird": "^3.5.5", - "check-types": "^11.1.1", - "hoopy": "^0.1.4", - "tryer": "^1.0.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "engines": { - "node": "*" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, - "node_modules/bn.js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", - "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==" - }, - "node_modules/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.8.1", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.7", - "raw-body": "2.4.3", - "type-is": "~1.6.18" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/bonjour": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", - "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", - "dependencies": { - "array-flatten": "^2.1.0", - "deep-equal": "^1.0.1", - "dns-equal": "^1.0.0", - "dns-txt": "^2.0.2", - "multicast-dns": "^6.0.1", - "multicast-dns-service-types": "^1.1.0" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" - }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" - }, - "node_modules/browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dependencies": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dependencies": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "node_modules/browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "dependencies": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/browserify-rsa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", - "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", - "dependencies": { - "bn.js": "^5.0.0", - "randombytes": "^2.0.1" - } - }, - "node_modules/browserify-sign": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", - "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", - "dependencies": { - "bn.js": "^5.1.1", - "browserify-rsa": "^4.0.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.3", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - } - }, - "node_modules/browserify-sign/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/browserify-sign/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dependencies": { - "pako": "~1.0.5" - } - }, - "node_modules/browserslist": { - "version": "4.19.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.3.tgz", - "integrity": "sha512-XK3X4xtKJ+Txj8G5c30B4gsm71s69lqXlkYui4s6EkKxuv49qjYlY6oVd+IFJ73d4YymtM3+djvvt/R/iJwwDg==", - "dependencies": { - "caniuse-lite": "^1.0.30001312", - "electron-to-chromium": "^1.4.71", - "escalade": "^3.1.1", - "node-releases": "^2.0.2", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "dependencies": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "node_modules/buffer-indexof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", - "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==" - }, - "node_modules/buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" - }, - "node_modules/builtin-modules": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", - "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=" - }, - "node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/cacache": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", - "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", - "dependencies": { - "@npmcli/fs": "^1.0.0", - "@npmcli/move-file": "^1.0.1", - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^6.0.0", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.1", - "tar": "^6.0.2", - "unique-filename": "^1.1.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/cacache/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", - "dependencies": { - "callsites": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/caller-callsite/node_modules/callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", - "engines": { - "node": ">=4" - } - }, - "node_modules/caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", - "dependencies": { - "caller-callsite": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "dependencies": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelize": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", - "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=", - "peer": true - }, - "node_modules/caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "dependencies": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001385", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001385.tgz", - "integrity": "sha512-MpiCqJGhBkHgpyimE9GWmZTnyHyEEM35u115bD3QBrXpjvL/JgcP8cUhKJshfmg4OtEHFenifcK5sZayEw5tvQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - } - ] - }, - "node_modules/capture-exit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", - "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", - "dependencies": { - "rsvp": "^4.8.4" - }, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/case-sensitive-paths-webpack-plugin": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.3.0.tgz", - "integrity": "sha512-/4YgnZS8y1UXXmC02xD5rRrBEu6T5ub+mQHLNRj0fzTRbgdBYhsNo2V5EqwgqrExjxsjtF/OpAKAMkKsxbD5XQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "engines": { - "node": ">=10" - } - }, - "node_modules/character-entities": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", - "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", - "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-reference-invalid": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", - "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/charcodes": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/charcodes/-/charcodes-0.2.0.tgz", - "integrity": "sha512-Y4kiDb+AM4Ecy58YkuZrrSRJBDQdQ2L+NyS1vHHFtNtUjgutcZfx3yp1dAONI/oPaPmyGfCLx5CxL+zauIMyKQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/check-types": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz", - "integrity": "sha512-tzWzvgePgLORb9/3a0YenggReLKAIb2owL03H2Xdoe5pKcUyWRSEQ8xfCar8t2SIAuEDwtmx2da1YB52YuHQMQ==" - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "optional": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "engines": { - "node": ">=10" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "engines": { - "node": ">=6.0" - } - }, - "node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" - }, - "node_modules/cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/cjs-module-lexer": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz", - "integrity": "sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==" - }, - "node_modules/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "node_modules/class-utils/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clean-css": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", - "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", - "dependencies": { - "source-map": "~0.6.0" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/clean-css/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "engines": { - "node": ">=6" - } - }, - "node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/clsx": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", - "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/coa": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", - "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", - "dependencies": { - "@types/q": "^1.5.1", - "chalk": "^2.4.1", - "q": "^1.1.2" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==" - }, - "node_modules/collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", - "dependencies": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "node_modules/color-string": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.0.tgz", - "integrity": "sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ==", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/common-tags": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", - "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" - }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" - }, - "node_modules/compose-function": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/compose-function/-/compose-function-3.0.3.tgz", - "integrity": "sha1-ntZ18TzFRQHTCVCkhv9qe6OrGF8=", - "dependencies": { - "arity-n": "^1.0.4" - } - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "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==", - "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==", - "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": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "engines": [ - "node >= 0.8" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/confusing-browser-globals": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", - "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==" - }, - "node_modules/connect-history-api-fallback": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", - "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==" - }, - "node_modules/constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=" - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-disposition/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "node_modules/copy-concurrently": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "dependencies": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" - } - }, - "node_modules/copy-concurrently/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/core-js": { - "version": "3.21.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.21.1.tgz", - "integrity": "sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig==", - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-compat": { - "version": "3.21.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.1.tgz", - "integrity": "sha512-gbgX5AUvMb8gwxC7FLVWYT7Kkgu/y7+h/h1X43yJkNqhlK2fuYyQimqvKGNZFAY6CKii/GFKJ2cp/1/42TN36g==", - "dependencies": { - "browserslist": "^4.19.1", - "semver": "7.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-compat/node_modules/semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/core-js-pure": { - "version": "3.21.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.21.1.tgz", - "integrity": "sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ==", - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "node_modules/cosmiconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", - "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "dependencies": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - } - }, - "node_modules/create-ecdh/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "node_modules/create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dependencies": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "node_modules/create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dependencies": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dependencies": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - }, - "engines": { - "node": "*" - } - }, - "node_modules/crypto-random-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", - "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", - "engines": { - "node": ">=4" - } - }, - "node_modules/css": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", - "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", - "dependencies": { - "inherits": "^2.0.3", - "source-map": "^0.6.1", - "source-map-resolve": "^0.5.2", - "urix": "^0.1.0" - } - }, - "node_modules/css-blank-pseudo": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz", - "integrity": "sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w==", - "dependencies": { - "postcss": "^7.0.5" - }, - "bin": { - "css-blank-pseudo": "cli.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/css-color-keywords": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", - "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=", - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/css-color-names": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", - "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", - "engines": { - "node": "*" - } - }, - "node_modules/css-declaration-sorter": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", - "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", - "dependencies": { - "postcss": "^7.0.1", - "timsort": "^0.3.0" - }, - "engines": { - "node": ">4" - } - }, - "node_modules/css-has-pseudo": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz", - "integrity": "sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ==", - "dependencies": { - "postcss": "^7.0.6", - "postcss-selector-parser": "^5.0.0-rc.4" - }, - "bin": { - "css-has-pseudo": "cli.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/css-has-pseudo/node_modules/cssesc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/css-has-pseudo/node_modules/postcss-selector-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", - "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", - "dependencies": { - "cssesc": "^2.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/css-loader": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-4.3.0.tgz", - "integrity": "sha512-rdezjCjScIrsL8BSYszgT4s476IcNKt6yX69t0pHjJVnPUTDpn4WfIpDQTN3wCJvUvfsz/mFjuGOekf3PY3NUg==", - "dependencies": { - "camelcase": "^6.0.0", - "cssesc": "^3.0.0", - "icss-utils": "^4.1.1", - "loader-utils": "^2.0.0", - "postcss": "^7.0.32", - "postcss-modules-extract-imports": "^2.0.0", - "postcss-modules-local-by-default": "^3.0.3", - "postcss-modules-scope": "^2.2.0", - "postcss-modules-values": "^3.0.0", - "postcss-value-parser": "^4.1.0", - "schema-utils": "^2.7.1", - "semver": "^7.3.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.27.0 || ^5.0.0" - } - }, - "node_modules/css-prefers-color-scheme": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz", - "integrity": "sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg==", - "dependencies": { - "postcss": "^7.0.5" - }, - "bin": { - "css-prefers-color-scheme": "cli.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/css-select": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.2.1.tgz", - "integrity": "sha512-/aUslKhzkTNCQUB2qTX84lVmfia9NyjP3WpDGtj/WxhwBzWBYUV3DgUpurHTme8UTPcPlAD1DJ+b0nN/t50zDQ==", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^5.1.0", - "domhandler": "^4.3.0", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-select-base-adapter": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", - "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" - }, - "node_modules/css-to-react-native": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz", - "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==", - "peer": true, - "dependencies": { - "camelize": "^1.0.0", - "css-color-keywords": "^1.0.0", - "postcss-value-parser": "^4.0.2" - } - }, - "node_modules/css-tree": { - "version": "1.0.0-alpha.37", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", - "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", - "dependencies": { - "mdn-data": "2.0.4", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/css-tree/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/css-vendor": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", - "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==", - "dependencies": { - "@babel/runtime": "^7.8.3", - "is-in-browser": "^1.0.2" - } - }, - "node_modules/css-what": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz", - "integrity": "sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw==", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cssdb": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz", - "integrity": "sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ==" - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cssnano": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.11.tgz", - "integrity": "sha512-6gZm2htn7xIPJOHY824ERgj8cNPgPxyCSnkXc4v7YvNW+TdVfzgngHcEhy/8D11kUWRUMbke+tC+AUcUsnMz2g==", - "dependencies": { - "cosmiconfig": "^5.0.0", - "cssnano-preset-default": "^4.0.8", - "is-resolvable": "^1.0.0", - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/cssnano-preset-default": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.8.tgz", - "integrity": "sha512-LdAyHuq+VRyeVREFmuxUZR1TXjQm8QQU/ktoo/x7bz+SdOge1YKc5eMN6pRW7YWBmyq59CqYba1dJ5cUukEjLQ==", - "dependencies": { - "css-declaration-sorter": "^4.0.1", - "cssnano-util-raw-cache": "^4.0.1", - "postcss": "^7.0.0", - "postcss-calc": "^7.0.1", - "postcss-colormin": "^4.0.3", - "postcss-convert-values": "^4.0.1", - "postcss-discard-comments": "^4.0.2", - "postcss-discard-duplicates": "^4.0.2", - "postcss-discard-empty": "^4.0.1", - "postcss-discard-overridden": "^4.0.1", - "postcss-merge-longhand": "^4.0.11", - "postcss-merge-rules": "^4.0.3", - "postcss-minify-font-values": "^4.0.2", - "postcss-minify-gradients": "^4.0.2", - "postcss-minify-params": "^4.0.2", - "postcss-minify-selectors": "^4.0.2", - "postcss-normalize-charset": "^4.0.1", - "postcss-normalize-display-values": "^4.0.2", - "postcss-normalize-positions": "^4.0.2", - "postcss-normalize-repeat-style": "^4.0.2", - "postcss-normalize-string": "^4.0.2", - "postcss-normalize-timing-functions": "^4.0.2", - "postcss-normalize-unicode": "^4.0.1", - "postcss-normalize-url": "^4.0.1", - "postcss-normalize-whitespace": "^4.0.2", - "postcss-ordered-values": "^4.1.2", - "postcss-reduce-initial": "^4.0.3", - "postcss-reduce-transforms": "^4.0.2", - "postcss-svgo": "^4.0.3", - "postcss-unique-selectors": "^4.0.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/cssnano-util-get-arguments": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", - "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/cssnano-util-get-match": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", - "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/cssnano-util-raw-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", - "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", - "dependencies": { - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/cssnano-util-same-parent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", - "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/cssnano/node_modules/cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "dependencies": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cssnano/node_modules/import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", - "dependencies": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cssnano/node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cssnano/node_modules/resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "engines": { - "node": ">=4" - } - }, - "node_modules/csso": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", - "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", - "dependencies": { - "css-tree": "^1.1.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/csso/node_modules/css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "dependencies": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/csso/node_modules/mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" - }, - "node_modules/csso/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==" - }, - "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dependencies": { - "cssom": "~0.3.6" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" - }, - "node_modules/csstype": { - "version": "2.6.19", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.19.tgz", - "integrity": "sha512-ZVxXaNy28/k3kJg0Fou5MiYpp88j7H9hLZp8PDC3jV0WFjfH5E9xHb56L0W59cPbKbcHXeP4qyT8PrHp8t6LcQ==" - }, - "node_modules/cyclist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", - "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=" - }, - "node_modules/d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dependencies": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "node_modules/damerau-levenshtein": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", - "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" - }, - "node_modules/data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dependencies": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/date-fns": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.19.0.tgz", - "integrity": "sha512-X3bf2iTPgCAQp9wvjOQytnf5vO5rESYRXlPIVcgSbtT5OTScPcsf9eZU+B/YIkKAtYr5WeCii58BgATrNitlWg==", - "engines": { - "node": ">=0.11" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" - } - }, - "node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decimal.js": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", - "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==" - }, - "node_modules/decode-uri-component": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=" - }, - "node_modules/deep-equal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", - "dependencies": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" - }, - "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-gateway": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", - "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", - "dependencies": { - "execa": "^1.0.0", - "ip-regex": "^2.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/default-gateway/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/default-gateway/node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/default-gateway/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/default-gateway/node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-gateway/node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/default-gateway/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "engines": { - "node": ">=4" - } - }, - "node_modules/default-gateway/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/default-gateway/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-gateway/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-gateway/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dependencies": { - "object-keys": "^1.0.12" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/del": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", - "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", - "dependencies": { - "@types/glob": "^7.1.1", - "globby": "^6.1.0", - "is-path-cwd": "^2.0.0", - "is-path-in-cwd": "^2.0.0", - "p-map": "^2.0.0", - "pify": "^4.0.1", - "rimraf": "^2.6.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/del/node_modules/array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dependencies": { - "array-uniq": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/del/node_modules/globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "dependencies": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/del/node_modules/globby/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/del/node_modules/p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/del/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/des.js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", - "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", - "dependencies": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" - }, - "node_modules/detect-port-alt": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", - "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", - "dependencies": { - "address": "^1.0.1", - "debug": "^2.6.0" - }, - "bin": { - "detect": "bin/detect-port", - "detect-port": "bin/detect-port" - }, - "engines": { - "node": ">= 4.2.1" - } - }, - "node_modules/detect-port-alt/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/detect-port-alt/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/diff-sequences": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dependencies": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - } - }, - "node_modules/diffie-hellman/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=" - }, - "node_modules/dns-packet": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz", - "integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==", - "dependencies": { - "ip": "^1.1.0", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/dns-txt": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", - "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", - "dependencies": { - "buffer-indexof": "^1.0.0" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dom-converter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", - "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", - "dependencies": { - "utila": "~0.4" - } - }, - "node_modules/dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "dependencies": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, - "node_modules/dom-helpers/node_modules/csstype": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", - "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==" - }, - "node_modules/dom-serializer": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", - "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "engines": { - "node": ">=0.4", - "npm": ">=1.2" - } - }, - "node_modules/domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dependencies": { - "webidl-conversions": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/domexception/node_modules/webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/domhandler": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.0.tgz", - "integrity": "sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g==", - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dot-prop/node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/dotenv": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", - "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/dotenv-expand": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", - "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==" - }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" - }, - "node_modules/duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "dependencies": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "node_modules/ejs": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", - "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==", - "hasInstallScript": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.4.73", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.73.tgz", - "integrity": "sha512-RlCffXkE/LliqfA5m29+dVDPB2r72y2D2egMMfIy3Le8ODrxjuZNVo4NIC2yPL01N4xb4nZQLwzi6Z5tGIGLnA==" - }, - "node_modules/elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/elliptic/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "node_modules/emittery": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", - "integrity": "sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" - }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/enhanced-resolve": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", - "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", - "dependencies": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.5.0", - "tapable": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/enhanced-resolve/node_modules/memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "dependencies": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - }, - "engines": { - "node": ">=4.3.0 <5.0.0 || >=5.10" - } - }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/error-stack-parser": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.7.tgz", - "integrity": "sha512-chLOW0ZGRf4s8raLrDxa5sdkvPec5YdvwbFnqJme4rk0rFajP8mPtrDL1+I+CwrQDCjswDA5sREX7jYQDQs9vA==", - "dependencies": { - "stackframe": "^1.1.1" - } - }, - "node_modules/es-abstract": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", - "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", - "dependencies": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-symbols": "^1.0.2", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.1", - "is-string": "^1.0.7", - "is-weakref": "^1.0.1", - "object-inspect": "^1.11.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es5-ext": { - "version": "0.10.53", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", - "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", - "dependencies": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" - } - }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "dependencies": { - "d": "^1.0.1", - "ext": "^1.1.2" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/escodegen/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "dependencies": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-react-app": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-6.0.0.tgz", - "integrity": "sha512-bpoAAC+YRfzq0dsTk+6v9aHm/uqnDwayNAXleMypGl6CpxI9oXXscVHo4fk3eJPIn+rsbtNetB4r/ZIidFIE8A==", - "dependencies": { - "confusing-browser-globals": "^1.0.10" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^4.0.0", - "@typescript-eslint/parser": "^4.0.0", - "babel-eslint": "^10.0.0", - "eslint": "^7.5.0", - "eslint-plugin-flowtype": "^5.2.0", - "eslint-plugin-import": "^2.22.0", - "eslint-plugin-jest": "^24.0.0", - "eslint-plugin-jsx-a11y": "^6.3.1", - "eslint-plugin-react": "^7.20.3", - "eslint-plugin-react-hooks": "^4.0.8", - "eslint-plugin-testing-library": "^3.9.0" - }, - "peerDependenciesMeta": { - "eslint-plugin-jest": { - "optional": true - }, - "eslint-plugin-testing-library": { - "optional": true - } - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", - "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", - "dependencies": { - "debug": "^3.2.7", - "resolve": "^1.20.0" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-module-utils": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz", - "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==", - "dependencies": { - "debug": "^3.2.7", - "find-up": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-module-utils/node_modules/find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dependencies": { - "locate-path": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-module-utils/node_modules/locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-module-utils/node_modules/p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dependencies": { - "p-try": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-module-utils/node_modules/p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dependencies": { - "p-limit": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-module-utils/node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-module-utils/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-plugin-flowtype": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-5.10.0.tgz", - "integrity": "sha512-vcz32f+7TP+kvTUyMXZmCnNujBQZDNmcqPImw8b9PZ+16w1Qdm6ryRuYZYVaG9xRqqmAPr2Cs9FAX5gN+x/bjw==", - "dependencies": { - "lodash": "^4.17.15", - "string-natural-compare": "^3.0.1" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "peerDependencies": { - "eslint": "^7.1.0" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.25.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz", - "integrity": "sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==", - "dependencies": { - "array-includes": "^3.1.4", - "array.prototype.flat": "^1.2.5", - "debug": "^2.6.9", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.2", - "has": "^1.0.3", - "is-core-module": "^2.8.0", - "is-glob": "^4.0.3", - "minimatch": "^3.0.4", - "object.values": "^1.1.5", - "resolve": "^1.20.0", - "tsconfig-paths": "^3.12.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/eslint-plugin-jest": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.7.0.tgz", - "integrity": "sha512-wUxdF2bAZiYSKBclsUMrYHH6WxiBreNjyDxbRv345TIvPeoCEgPNEn3Sa+ZrSqsf1Dl9SqqSREXMHExlMMu1DA==", - "dependencies": { - "@typescript-eslint/experimental-utils": "^4.0.1" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": ">= 4", - "eslint": ">=5" - }, - "peerDependenciesMeta": { - "@typescript-eslint/eslint-plugin": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-jsx-a11y": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.5.1.tgz", - "integrity": "sha512-sVCFKX9fllURnXT2JwLN5Qgo24Ug5NF6dxhkmxsMEUZhXRcGg+X3e1JbJ84YePQKBl5E0ZjAH5Q4rkdcGY99+g==", - "dependencies": { - "@babel/runtime": "^7.16.3", - "aria-query": "^4.2.2", - "array-includes": "^3.1.4", - "ast-types-flow": "^0.0.7", - "axe-core": "^4.3.5", - "axobject-query": "^2.2.0", - "damerau-levenshtein": "^1.0.7", - "emoji-regex": "^9.2.2", - "has": "^1.0.3", - "jsx-ast-utils": "^3.2.1", - "language-tags": "^1.0.5", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=4.0" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" - } - }, - "node_modules/eslint-plugin-react": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.29.1.tgz", - "integrity": "sha512-WtzRpHMhsOX05ZrkyaaqmLl2uXGqmYooCfBxftJKlkYdsltiufGgfU7uuoHwR2lBam2Kh/EIVID4aU9e3kbCMA==", - "dependencies": { - "array-includes": "^3.1.4", - "array.prototype.flatmap": "^1.2.5", - "doctrine": "^2.1.0", - "estraverse": "^5.3.0", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.5", - "object.fromentries": "^2.0.5", - "object.hasown": "^1.1.0", - "object.values": "^1.1.5", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.3", - "semver": "^6.3.0", - "string.prototype.matchall": "^4.0.6" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.3.0.tgz", - "integrity": "sha512-XslZy0LnMn+84NEG9jSGR6eGqaZB3133L8xewQo3fQagbQuGt7a63gf+P1NGKZavEYEC3UXaWEAA/AqDkuN6xA==", - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.3", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz", - "integrity": "sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==", - "dependencies": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/eslint-plugin-react/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-plugin-testing-library": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-3.10.2.tgz", - "integrity": "sha512-WAmOCt7EbF1XM8XfbCKAEzAPnShkNSwcIsAD2jHdsMUT9mZJPjLCG7pMzbcC8kK366NOuGip8HKLDC+Xk4yIdA==", - "dependencies": { - "@typescript-eslint/experimental-utils": "^3.10.1" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0", - "npm": ">=6" - }, - "peerDependencies": { - "eslint": "^5 || ^6 || ^7" - } - }, - "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/experimental-utils": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz", - "integrity": "sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==", - "dependencies": { - "@types/json-schema": "^7.0.3", - "@typescript-eslint/types": "3.10.1", - "@typescript-eslint/typescript-estree": "3.10.1", - "eslint-scope": "^5.0.0", - "eslint-utils": "^2.0.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - } - }, - "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/types": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz", - "integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==", - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/typescript-estree": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz", - "integrity": "sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==", - "dependencies": { - "@typescript-eslint/types": "3.10.1", - "@typescript-eslint/visitor-keys": "3.10.1", - "debug": "^4.1.1", - "glob": "^7.1.6", - "is-glob": "^4.0.1", - "lodash": "^4.17.15", - "semver": "^7.3.2", - "tsutils": "^3.17.1" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/visitor-keys": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz", - "integrity": "sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==", - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-testing-library/node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-plugin-testing-library/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-scope/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-webpack-plugin": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-2.6.0.tgz", - "integrity": "sha512-V+LPY/T3kur5QO3u+1s34VDTcRxjXWPUGM4hlmTb5DwVD0OQz631yGTxJZf4SpAqAjdbBVe978S8BJeHpAdOhQ==", - "dependencies": { - "@types/eslint": "^7.28.2", - "arrify": "^2.0.1", - "jest-worker": "^27.3.1", - "micromatch": "^4.0.4", - "normalize-path": "^3.0.0", - "schema-utils": "^3.1.1" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0", - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/eslint-webpack-plugin/node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/eslint/node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint/node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint/node_modules/globals": { - "version": "13.12.1", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", - "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==" - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/eventsource": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.2.tgz", - "integrity": "sha512-xAH3zWhgO2/3KIniEKYPr8plNSzlGINOUqYj0m0u7AB81iRw8b/3E73W6AuU+6klLbaSFmZnaETQ2lXPfAydrA==", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dependencies": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/exec-sh": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz", - "integrity": "sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==" - }, - "node_modules/execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dependencies": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "node_modules/expand-brackets/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/expect": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz", - "integrity": "sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==", - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-styles": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/expect/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/expect/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/expect/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/express": { - "version": "4.17.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", - "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.19.2", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.4.2", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.9.7", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.17.2", - "serve-static": "1.14.2", - "setprototypeof": "1.2.0", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express/node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/express/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ext": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", - "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", - "dependencies": { - "type": "^2.5.0" - } - }, - "node_modules/ext/node_modules/type": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.6.0.tgz", - "integrity": "sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ==" - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "node_modules/fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" - }, - "node_modules/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/figgy-pudding": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", - "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==" - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/file-loader": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.1.1.tgz", - "integrity": "sha512-Klt8C4BjWSXYQAfhpYYkG4qHNTna4toMHEbWrI5IuVoxbU6uiDKeKAP99R8mmbJi3lvewn/jQBOgU4+NS3tDQw==", - "dependencies": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/file-loader/node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/filesize": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz", - "integrity": "sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "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==", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", - "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==" - }, - "node_modules/flatten": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.3.tgz", - "integrity": "sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==", - "deprecated": "flatten is deprecated in favor of utility frameworks such as lodash." - }, - "node_modules/flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "dependencies": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - } - }, - "node_modules/follow-redirects": { - "version": "1.14.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", - "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fork-ts-checker-webpack-plugin": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-4.1.6.tgz", - "integrity": "sha512-DUxuQaKoqfNne8iikd14SAkh5uw4+8vNifp6gmA73yYNS6ywLIWSLD/n/mBzHQRpW3J7rbATEakmiA8JvkTyZw==", - "dependencies": { - "@babel/code-frame": "^7.5.5", - "chalk": "^2.4.1", - "micromatch": "^3.1.10", - "minimatch": "^3.0.4", - "semver": "^5.6.0", - "tapable": "^1.0.0", - "worker-rpc": "^0.1.0" - }, - "engines": { - "node": ">=6.11.5", - "yarn": ">=1.0.0" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dependencies": { - "map-cache": "^0.2.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "dependencies": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-write-stream-atomic": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", - "dependencies": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-own-enumerable-property-symbols": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==" - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "dependencies": { - "global-prefix": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "dependencies": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/global-prefix/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" - }, - "node_modules/growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", - "optional": true - }, - "node_modules/gzip-size": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", - "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==", - "dependencies": { - "duplexer": "^0.1.1", - "pify": "^4.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" - }, - "node_modules/harmony-reflect": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz", - "integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==" - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "engines": { - "node": ">=4" - } - }, - "node_modules/has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "node_modules/has-values/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/hash-base/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/hash-base/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "bin": { - "he": "bin/he" - } - }, - "node_modules/hex-color-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", - "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" - }, - "node_modules/history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", - "dependencies": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" - } - }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/hoopy": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", - "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==", - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" - }, - "node_modules/hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", - "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "node_modules/hsl-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", - "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=" - }, - "node_modules/hsla-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", - "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=" - }, - "node_modules/html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dependencies": { - "whatwg-encoding": "^1.0.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/html-entities": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.4.0.tgz", - "integrity": "sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==" - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" - }, - "node_modules/html-minifier-terser": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", - "integrity": "sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==", - "dependencies": { - "camel-case": "^4.1.1", - "clean-css": "^4.2.3", - "commander": "^4.1.1", - "he": "^1.2.0", - "param-case": "^3.0.3", - "relateurl": "^0.2.7", - "terser": "^4.6.3" - }, - "bin": { - "html-minifier-terser": "cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/html-to-react": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/html-to-react/-/html-to-react-1.4.8.tgz", - "integrity": "sha512-BDOPUYTej5eiOco0V0wxJ5FS+Zckp2O0kb13X1SxQFzyusIeUmnxAK0Cl5M6692bmGBs1WBjh9qF3oQ2AJUZmg==", - "dependencies": { - "domhandler": "^4.0", - "htmlparser2": "^7.0", - "lodash.camelcase": "^4.3.0", - "ramda": "^0.28.0" - }, - "peerDependencies": { - "react": "^16.0 || ^17.0" - } - }, - "node_modules/html-webpack-plugin": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.5.0.tgz", - "integrity": "sha512-MouoXEYSjTzCrjIxWwg8gxL5fE2X2WZJLmBYXlaJhQUH5K/b5OrqmV7T4dB7iu0xkmJ6JlUuV6fFVtnqbPopZw==", - "dependencies": { - "@types/html-minifier-terser": "^5.0.0", - "@types/tapable": "^1.0.5", - "@types/webpack": "^4.41.8", - "html-minifier-terser": "^5.0.1", - "loader-utils": "^1.2.3", - "lodash": "^4.17.15", - "pretty-error": "^2.1.1", - "tapable": "^1.1.3", - "util.promisify": "1.0.0" - }, - "engines": { - "node": ">=6.9" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/html-webpack-plugin/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/html-webpack-plugin/node_modules/loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/htmlparser2": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", - "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.2", - "domutils": "^2.8.0", - "entities": "^3.0.1" - } - }, - "node_modules/htmlparser2/node_modules/entities": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", - "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=" - }, - "node_modules/http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/http-parser-js": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.5.tgz", - "integrity": "sha512-x+JVEkO2PoM8qqpbPbOL3cqHPwerep7OwzK7Ay+sMQjKzaKCqWvjoXm5tqMP9tXWWTnTzAjIhXg+J99XYuPhPA==" - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/http-proxy-middleware": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", - "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", - "dependencies": { - "http-proxy": "^1.17.0", - "is-glob": "^4.0.0", - "lodash": "^4.17.11", - "micromatch": "^3.1.10" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "node_modules/http-proxy-middleware/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" - }, - "node_modules/https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "engines": { - "node": ">=8.12.0" - } - }, - "node_modules/hyphenate-style-name": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", - "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==" - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/icss-utils": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", - "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", - "dependencies": { - "postcss": "^7.0.14" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/identity-obj-proxy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", - "integrity": "sha1-lNK9qWCERT7zb7xarsN+D3nx/BQ=", - "dependencies": { - "harmony-reflect": "^1.4.6" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" - }, - "node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/immer": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/immer/-/immer-8.0.1.tgz", - "integrity": "sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, - "node_modules/import-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", - "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", - "dependencies": { - "import-from": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "engines": { - "node": ">=4" - } - }, - "node_modules/import-from": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", - "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", - "dependencies": { - "resolve-from": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/import-from/node_modules/resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "engines": { - "node": ">=4" - } - }, - "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-local/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/indexes-of": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", - "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=" - }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==" - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, - "node_modules/internal-ip": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", - "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", - "dependencies": { - "default-gateway": "^4.2.0", - "ipaddr.js": "^1.9.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "dependencies": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" - }, - "node_modules/ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", - "engines": { - "node": ">=4" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-absolute-url": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", - "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-alphabetical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", - "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-alphanumerical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", - "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", - "dependencies": { - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "optional": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "engines": { - "node": ">=4" - } - }, - "node_modules/is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dependencies": { - "ci-info": "^2.0.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "node_modules/is-color-stop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", - "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", - "dependencies": { - "css-color-names": "^0.0.4", - "hex-color-regex": "^1.1.0", - "hsl-regex": "^1.0.0", - "hsla-regex": "^1.0.0", - "rgb-regex": "^1.0.1", - "rgba-regex": "^1.0.0" - } - }, - "node_modules/is-core-module": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", - "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-decimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", - "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-hexadecimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", - "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-in-browser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", - "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=" - }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=" - }, - "node_modules/is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", - "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-path-in-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", - "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", - "dependencies": { - "is-path-inside": "^2.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-path-inside": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", - "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", - "dependencies": { - "path-is-inside": "^1.0.2" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==" - }, - "node_modules/is-root": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", - "integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", - "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz", - "integrity": "sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q==", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/istanbul-lib-report/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz", - "integrity": "sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest": { - "version": "26.6.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-26.6.0.tgz", - "integrity": "sha512-jxTmrvuecVISvKFFhOkjsWRZV7sFqdSUAd1ajOKY+/QE/aLBVstsJ/dX8GczLzwiT6ZEwwmZqtCUHLHHQVzcfA==", - "dependencies": { - "@jest/core": "^26.6.0", - "import-local": "^3.0.2", - "jest-cli": "^26.6.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-changed-files": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz", - "integrity": "sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==", - "dependencies": { - "@jest/types": "^26.6.2", - "execa": "^4.0.0", - "throat": "^5.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-circus": { - "version": "26.6.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-26.6.0.tgz", - "integrity": "sha512-L2/Y9szN6FJPWFK8kzWXwfp+FOR7xq0cUL4lIsdbIdwz3Vh6P1nrpcqOleSzr28zOtSHQNV9Z7Tl+KkuK7t5Ng==", - "dependencies": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^26.6.0", - "@jest/test-result": "^26.6.0", - "@jest/types": "^26.6.0", - "@types/babel__traverse": "^7.0.4", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "expect": "^26.6.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^26.6.0", - "jest-matcher-utils": "^26.6.0", - "jest-message-util": "^26.6.0", - "jest-runner": "^26.6.0", - "jest-runtime": "^26.6.0", - "jest-snapshot": "^26.6.0", - "jest-util": "^26.6.0", - "pretty-format": "^26.6.0", - "stack-utils": "^2.0.2", - "throat": "^5.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-circus/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-circus/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-circus/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-circus/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-cli": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz", - "integrity": "sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==", - "dependencies": { - "@jest/core": "^26.6.3", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "is-ci": "^2.0.0", - "jest-config": "^26.6.3", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "prompts": "^2.0.1", - "yargs": "^15.4.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-cli/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-cli/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-cli/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-cli/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-cli/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", - "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==", - "dependencies": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^26.6.3", - "@jest/types": "^26.6.2", - "babel-jest": "^26.6.3", - "chalk": "^4.0.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-environment-jsdom": "^26.6.2", - "jest-environment-node": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-jasmine2": "^26.6.3", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - }, - "peerDependencies": { - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-config/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-config/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-config/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config/node_modules/jest-resolve": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", - "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", - "dependencies": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^26.6.2", - "read-pkg-up": "^7.0.1", - "resolve": "^1.18.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-config/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", - "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-diff/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-diff/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-docblock": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", - "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-each": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz", - "integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==", - "dependencies": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-each/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-each/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-each/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-each/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-jsdom": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz", - "integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==", - "dependencies": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2", - "jsdom": "^16.4.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-environment-node": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz", - "integrity": "sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==", - "dependencies": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-haste-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", - "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==", - "dependencies": { - "@jest/types": "^26.6.2", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^26.0.0", - "jest-serializer": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "micromatch": "^4.0.2", - "sane": "^4.0.3", - "walker": "^1.0.7" - }, - "engines": { - "node": ">= 10.14.2" - }, - "optionalDependencies": { - "fsevents": "^2.1.2" - } - }, - "node_modules/jest-haste-map/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-haste-map/node_modules/jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-haste-map/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-jasmine2": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", - "integrity": "sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==", - "dependencies": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^26.6.2", - "is-generator-fn": "^2.0.0", - "jest-each": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2", - "throat": "^5.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-jasmine2/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-jasmine2/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-jasmine2/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-jasmine2/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-jasmine2/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-leak-detector": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz", - "integrity": "sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==", - "dependencies": { - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-matcher-utils": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", - "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-matcher-utils/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-matcher-utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", - "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "@jest/types": "^26.6.2", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2", - "slash": "^3.0.0", - "stack-utils": "^2.0.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-message-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-message-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-mock": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz", - "integrity": "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==", - "dependencies": { - "@jest/types": "^26.6.2", - "@types/node": "*" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", - "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-resolve": { - "version": "26.6.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.0.tgz", - "integrity": "sha512-tRAz2bwraHufNp+CCmAD8ciyCpXCs1NQxB5EJAmtCFy6BN81loFEGWKzYu26Y62lAJJe4X4jg36Kf+NsQyiStQ==", - "dependencies": { - "@jest/types": "^26.6.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^26.6.0", - "read-pkg-up": "^7.0.1", - "resolve": "^1.17.0", - "slash": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz", - "integrity": "sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg==", - "dependencies": { - "@jest/types": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-snapshot": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-resolve/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-resolve/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-resolve/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runner": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz", - "integrity": "sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==", - "dependencies": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.7.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-docblock": "^26.0.0", - "jest-haste-map": "^26.6.2", - "jest-leak-detector": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "source-map-support": "^0.5.6", - "throat": "^5.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-runner/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runner/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runner/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runner/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-runner/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runner/node_modules/jest-resolve": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", - "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", - "dependencies": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^26.6.2", - "read-pkg-up": "^7.0.1", - "resolve": "^1.18.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-runner/node_modules/jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-runner/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz", - "integrity": "sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==", - "dependencies": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/globals": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0", - "cjs-module-lexer": "^0.6.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "slash": "^3.0.0", - "strip-bom": "^4.0.0", - "yargs": "^15.4.1" - }, - "bin": { - "jest-runtime": "bin/jest-runtime.js" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-runtime/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runtime/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-runtime/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/jest-resolve": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", - "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", - "dependencies": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^26.6.2", - "read-pkg-up": "^7.0.1", - "resolve": "^1.18.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-runtime/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-serializer": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz", - "integrity": "sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==", - "dependencies": { - "@types/node": "*", - "graceful-fs": "^4.2.4" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-snapshot": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz", - "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==", - "dependencies": { - "@babel/types": "^7.0.0", - "@jest/types": "^26.6.2", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.0.0", - "chalk": "^4.0.0", - "expect": "^26.6.2", - "graceful-fs": "^4.2.4", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-haste-map": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", - "natural-compare": "^1.4.0", - "pretty-format": "^26.6.2", - "semver": "^7.3.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-snapshot/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-snapshot/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-snapshot/node_modules/jest-resolve": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", - "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", - "dependencies": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^26.6.2", - "read-pkg-up": "^7.0.1", - "resolve": "^1.18.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-snapshot/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", - "dependencies": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", - "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==", - "dependencies": { - "@jest/types": "^26.6.2", - "camelcase": "^6.0.0", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "leven": "^3.1.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-validate/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-validate/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watch-typeahead": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-0.6.1.tgz", - "integrity": "sha512-ITVnHhj3Jd/QkqQcTqZfRgjfyRhDFM/auzgVo2RKvSwi18YMvh0WvXDJFoFED6c7jd/5jxtu4kSOb9PTu2cPVg==", - "dependencies": { - "ansi-escapes": "^4.3.1", - "chalk": "^4.0.0", - "jest-regex-util": "^26.0.0", - "jest-watcher": "^26.3.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "jest": "^26.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-watch-typeahead/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-watch-typeahead/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-watch-typeahead/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watch-typeahead/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.6.2.tgz", - "integrity": "sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ==", - "dependencies": { - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^26.6.2", - "string-length": "^4.0.1" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-watcher/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-watcher/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-watcher/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "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==", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", - "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsdom/node_modules/acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" - }, - "node_modules/json3": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", - "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jss": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/jss/-/jss-10.9.0.tgz", - "integrity": "sha512-YpzpreB6kUunQBbrlArlsMpXYyndt9JATbt95tajx0t4MTJJcCJdd4hdNpHmOIDiUJrF/oX5wtVFrS3uofWfGw==", - "dependencies": { - "@babel/runtime": "^7.3.1", - "csstype": "^3.0.2", - "is-in-browser": "^1.1.3", - "tiny-warning": "^1.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/jss" - } - }, - "node_modules/jss-plugin-camel-case": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.9.0.tgz", - "integrity": "sha512-UH6uPpnDk413/r/2Olmw4+y54yEF2lRIV8XIZyuYpgPYTITLlPOsq6XB9qeqv+75SQSg3KLocq5jUBXW8qWWww==", - "dependencies": { - "@babel/runtime": "^7.3.1", - "hyphenate-style-name": "^1.0.3", - "jss": "10.9.0" - } - }, - "node_modules/jss-plugin-default-unit": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.9.0.tgz", - "integrity": "sha512-7Ju4Q9wJ/MZPsxfu4T84mzdn7pLHWeqoGd/D8O3eDNNJ93Xc8PxnLmV8s8ZPNRYkLdxZqKtm1nPQ0BM4JRlq2w==", - "dependencies": { - "@babel/runtime": "^7.3.1", - "jss": "10.9.0" - } - }, - "node_modules/jss-plugin-global": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.9.0.tgz", - "integrity": "sha512-4G8PHNJ0x6nwAFsEzcuVDiBlyMsj2y3VjmFAx/uHk/R/gzJV+yRHICjT4MKGGu1cJq2hfowFWCyrr/Gg37FbgQ==", - "dependencies": { - "@babel/runtime": "^7.3.1", - "jss": "10.9.0" - } - }, - "node_modules/jss-plugin-nested": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.9.0.tgz", - "integrity": "sha512-2UJnDrfCZpMYcpPYR16oZB7VAC6b/1QLsRiAutOt7wJaaqwCBvNsosLEu/fUyKNQNGdvg2PPJFDO5AX7dwxtoA==", - "dependencies": { - "@babel/runtime": "^7.3.1", - "jss": "10.9.0", - "tiny-warning": "^1.0.2" - } - }, - "node_modules/jss-plugin-props-sort": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.9.0.tgz", - "integrity": "sha512-7A76HI8bzwqrsMOJTWKx/uD5v+U8piLnp5bvru7g/3ZEQOu1+PjHvv7bFdNO3DwNPC9oM0a//KwIJsIcDCjDzw==", - "dependencies": { - "@babel/runtime": "^7.3.1", - "jss": "10.9.0" - } - }, - "node_modules/jss-plugin-rule-value-function": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.9.0.tgz", - "integrity": "sha512-IHJv6YrEf8pRzkY207cPmdbBstBaE+z8pazhPShfz0tZSDtRdQua5jjg6NMz3IbTasVx9FdnmptxPqSWL5tyJg==", - "dependencies": { - "@babel/runtime": "^7.3.1", - "jss": "10.9.0", - "tiny-warning": "^1.0.2" - } - }, - "node_modules/jss-plugin-vendor-prefixer": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.9.0.tgz", - "integrity": "sha512-MbvsaXP7iiVdYVSEoi+blrW+AYnTDvHTW6I6zqi7JcwXdc6I9Kbm234nEblayhF38EftoenbM+5218pidmC5gA==", - "dependencies": { - "@babel/runtime": "^7.3.1", - "css-vendor": "^2.0.8", - "jss": "10.9.0" - } - }, - "node_modules/jss/node_modules/csstype": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", - "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==" - }, - "node_modules/jsx-ast-utils": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz", - "integrity": "sha512-uP5vu8xfy2F9A6LGC22KO7e2/vGTS1MhP+18f++ZNlf0Ohaxbc9nIEwHAsejlJKyzfZzU5UIhe5ItYkitcZnZA==", - "dependencies": { - "array-includes": "^3.1.3", - "object.assign": "^4.1.2" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/killable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", - "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==" - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "engines": { - "node": ">=6" - } - }, - "node_modules/klona": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz", - "integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/language-subtag-registry": { - "version": "0.3.21", - "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz", - "integrity": "sha512-L0IqwlIXjilBVVYKFT37X9Ih11Um5NEl9cbJIuU/SwP/zEEAbBPOnEeeuxVMf45ydWQRDQN3Nqc96OgbH1K+Pg==" - }, - "node_modules/language-tags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", - "integrity": "sha1-0yHbxNowuovzAk4ED6XBRmH5GTo=", - "dependencies": { - "language-subtag-registry": "~0.3.2" - } - }, - "node_modules/last-call-webpack-plugin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz", - "integrity": "sha512-7KI2l2GIZa9p2spzPIVZBYyNKkN+e/SQPpnjlTiPhdbDW3F86tdKKELxKpzJ5sgU19wQWsACULZmpTPYHeWO5w==", - "dependencies": { - "lodash": "^4.17.5", - "webpack-sources": "^1.1.0" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" - }, - "node_modules/loader-runner": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", - "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", - "engines": { - "node": ">=4.3.0 <5.0.0 || >=5.10" - } - }, - "node_modules/loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "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==", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" - }, - "node_modules/lodash._reinterpolate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" - }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" - }, - "node_modules/lodash.template": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", - "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", - "dependencies": { - "lodash._reinterpolate": "^3.0.0", - "lodash.templatesettings": "^4.0.0" - } - }, - "node_modules/lodash.templatesettings": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", - "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", - "dependencies": { - "lodash._reinterpolate": "^3.0.0" - } - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=" - }, - "node_modules/lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" - }, - "node_modules/loglevel": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz", - "integrity": "sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA==", - "engines": { - "node": ">= 0.6.0" - }, - "funding": { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/loglevel" - } - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/magic-string": { - "version": "0.25.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", - "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", - "dependencies": { - "sourcemap-codec": "^1.4.4" - } - }, - "node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dependencies": { - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/mdast-add-list-metadata": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdast-add-list-metadata/-/mdast-add-list-metadata-1.0.1.tgz", - "integrity": "sha512-fB/VP4MJ0LaRsog7hGPxgOrSL3gE/2uEdZyDuSEnKCv/8IkYHiDkIQSbChiJoHyxZZXZ9bzckyRk+vNxFzh8rA==", - "dependencies": { - "unist-util-visit-parents": "1.1.2" - } - }, - "node_modules/mdast-util-from-markdown": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz", - "integrity": "sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==", - "dependencies": { - "@types/mdast": "^3.0.0", - "mdast-util-to-string": "^2.0.0", - "micromark": "~2.11.0", - "parse-entities": "^2.0.0", - "unist-util-stringify-position": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", - "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdn-data": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", - "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==" - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "dependencies": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/microevent.ts": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/microevent.ts/-/microevent.ts-0.1.1.tgz", - "integrity": "sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g==" - }, - "node_modules/micromark": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz", - "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "debug": "^4.0.0", - "parse-entities": "^2.0.0" - } - }, - "node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dependencies": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "bin": { - "miller-rabin": "bin/miller-rabin" - } - }, - "node_modules/miller-rabin/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.34", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", - "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", - "dependencies": { - "mime-db": "1.51.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/mini-create-react-context": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", - "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==", - "dependencies": { - "@babel/runtime": "^7.12.1", - "tiny-warning": "^1.0.3" - }, - "peerDependencies": { - "prop-types": "^15.0.0", - "react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, - "node_modules/mini-css-extract-plugin": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.3.tgz", - "integrity": "sha512-n9BA8LonkOkW1/zn+IbLPQmovsL0wMb9yx75fMJQZf2X1Zoec9yTZtyMePcyu19wPkmFbzZZA6fLTotpFhQsOA==", - "dependencies": { - "loader-utils": "^1.1.0", - "normalize-url": "1.9.1", - "schema-utils": "^1.0.0", - "webpack-sources": "^1.1.0" - }, - "engines": { - "node": ">= 6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.4.0 || ^5.0.0" - } - }, - "node_modules/mini-css-extract-plugin/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/mini-css-extract-plugin/node_modules/loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dependencies": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" - }, - "node_modules/minipass": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz", - "integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/mississippi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", - "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", - "dependencies": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", - "engines": { - "node": "*" - } - }, - "node_modules/move-concurrently": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", - "dependencies": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" - } - }, - "node_modules/move-concurrently/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/multicast-dns": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", - "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", - "dependencies": { - "dns-packet": "^1.3.1", - "thunky": "^1.0.2" - }, - "bin": { - "multicast-dns": "cli.js" - } - }, - "node_modules/multicast-dns-service-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", - "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=" - }, - "node_modules/nanoclone": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz", - "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==" - }, - "node_modules/nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/native-url": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/native-url/-/native-url-0.2.6.tgz", - "integrity": "sha512-k4bDC87WtgrdD362gZz6zoiXQrl40kYlBmpfmSjwRO1VU0V5ccwJTlxuE72F6m3V0vc1xOf6n3UCP9QyerRqmA==", - "dependencies": { - "querystring": "^0.2.0" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" - }, - "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==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" - }, - "node_modules/next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" - }, - "node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node_modules/node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=" - }, - "node_modules/node-libs-browser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", - "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", - "dependencies": { - "assert": "^1.1.1", - "browserify-zlib": "^0.2.0", - "buffer": "^4.3.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.1", - "events": "^3.0.0", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "path-browserify": "0.0.1", - "process": "^0.11.10", - "punycode": "^1.2.4", - "querystring-es3": "^0.2.0", - "readable-stream": "^2.3.3", - "stream-browserify": "^2.0.1", - "stream-http": "^2.7.2", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", - "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.11.0", - "vm-browserify": "^1.0.1" - } - }, - "node_modules/node-libs-browser/node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - }, - "node_modules/node-notifier": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.2.tgz", - "integrity": "sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg==", - "optional": true, - "dependencies": { - "growly": "^1.3.0", - "is-wsl": "^2.2.0", - "semver": "^7.3.2", - "shellwords": "^0.1.1", - "uuid": "^8.3.0", - "which": "^2.0.2" - } - }, - "node_modules/node-releases": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", - "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==" - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-url": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", - "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", - "dependencies": { - "object-assign": "^4.0.1", - "prepend-http": "^1.0.0", - "query-string": "^4.1.0", - "sort-keys": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nth-check": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz", - "integrity": "sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/num2fraction": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=" - }, - "node_modules/nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==" - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "node_modules/object-copy/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.entries": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", - "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.fromentries": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", - "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.getownpropertydescriptors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz", - "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.hasown": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.0.tgz", - "integrity": "sha512-MhjYRfj3GBlhSkDHo6QmvgjRLXQ2zndabdf3nX0yTyZK9rPfxb6uRpAac8HXNLy1GpqWtZ81Qh4v3uOls2sRAg==", - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.values": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", - "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" - }, - "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "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==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", - "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", - "dependencies": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/opn": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", - "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", - "dependencies": { - "is-wsl": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/opn/node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "engines": { - "node": ">=4" - } - }, - "node_modules/optimize-css-assets-webpack-plugin": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.4.tgz", - "integrity": "sha512-wqd6FdI2a5/FdoiCNNkEvLeA//lHHfG24Ln2Xm2qqdIk4aOlsR18jwpyOihqQ8849W3qu2DX8fOYxpvTMj+93A==", - "dependencies": { - "cssnano": "^4.1.10", - "last-call-webpack-plugin": "^3.0.0" - }, - "peerDependencies": { - "webpack": "^4.0.0" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=" - }, - "node_modules/p-each-series": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", - "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "engines": { - "node": ">=4" - } - }, - "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==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-retry": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", - "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", - "dependencies": { - "retry": "^0.12.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" - }, - "node_modules/parallel-transform": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", - "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", - "dependencies": { - "cyclist": "^1.0.1", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" - } - }, - "node_modules/param-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-asn1": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", - "dependencies": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/parse-entities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", - "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", - "dependencies": { - "character-entities": "^1.0.0", - "character-entities-legacy": "^1.0.0", - "character-reference-invalid": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-hexadecimal": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", - "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==" - }, - "node_modules/path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "engines": { - "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": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "dependencies": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "engines": { - "node": ">=6" - } - }, - "node_modules/pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dependencies": { - "pinkie": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "engines": { - "node": ">=4" - } - }, - "node_modules/pkg-up": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", - "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-up/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "engines": { - "node": ">=4" - } - }, - "node_modules/pnp-webpack-plugin": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz", - "integrity": "sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg==", - "dependencies": { - "ts-pnp": "^1.1.6" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/popper.js": { - "version": "1.16.1-lts", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1-lts.tgz", - "integrity": "sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA==" - }, - "node_modules/portfinder": { - "version": "1.0.28", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", - "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", - "dependencies": { - "async": "^2.6.2", - "debug": "^3.1.1", - "mkdirp": "^0.5.5" - }, - "engines": { - "node": ">= 0.12.0" - } - }, - "node_modules/portfinder/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "dependencies": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - } - }, - "node_modules/postcss-attribute-case-insensitive": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz", - "integrity": "sha512-clkFxk/9pcdb4Vkn0hAHq3YnxBQ2p0CGD1dy24jN+reBck+EWxMbxSUqN4Yj7t0w8csl87K6p0gxBe1utkJsYA==", - "dependencies": { - "postcss": "^7.0.2", - "postcss-selector-parser": "^6.0.2" - } - }, - "node_modules/postcss-browser-comments": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-browser-comments/-/postcss-browser-comments-3.0.0.tgz", - "integrity": "sha512-qfVjLfq7HFd2e0HW4s1dvU8X080OZdG46fFbIBFjW7US7YPDcWfRvdElvwMJr2LI6hMmD+7LnH2HcmXTs+uOig==", - "dependencies": { - "postcss": "^7" - }, - "engines": { - "node": ">=8.0.0" - }, - "peerDependencies": { - "browserslist": "^4" - } - }, - "node_modules/postcss-calc": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.5.tgz", - "integrity": "sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg==", - "dependencies": { - "postcss": "^7.0.27", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.0.2" - } - }, - "node_modules/postcss-color-functional-notation": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz", - "integrity": "sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g==", - "dependencies": { - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-color-gray": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz", - "integrity": "sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw==", - "dependencies": { - "@csstools/convert-colors": "^1.4.0", - "postcss": "^7.0.5", - "postcss-values-parser": "^2.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-color-hex-alpha": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz", - "integrity": "sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw==", - "dependencies": { - "postcss": "^7.0.14", - "postcss-values-parser": "^2.0.1" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-color-mod-function": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz", - "integrity": "sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ==", - "dependencies": { - "@csstools/convert-colors": "^1.4.0", - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-color-rebeccapurple": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz", - "integrity": "sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g==", - "dependencies": { - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-colormin": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", - "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", - "dependencies": { - "browserslist": "^4.0.0", - "color": "^3.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-colormin/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - }, - "node_modules/postcss-convert-values": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", - "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", - "dependencies": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-convert-values/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - }, - "node_modules/postcss-custom-media": { - "version": "7.0.8", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz", - "integrity": "sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg==", - "dependencies": { - "postcss": "^7.0.14" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-custom-properties": { - "version": "8.0.11", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz", - "integrity": "sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA==", - "dependencies": { - "postcss": "^7.0.17", - "postcss-values-parser": "^2.0.1" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-custom-selectors": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz", - "integrity": "sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w==", - "dependencies": { - "postcss": "^7.0.2", - "postcss-selector-parser": "^5.0.0-rc.3" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-custom-selectors/node_modules/cssesc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-custom-selectors/node_modules/postcss-selector-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", - "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", - "dependencies": { - "cssesc": "^2.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-dir-pseudo-class": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz", - "integrity": "sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw==", - "dependencies": { - "postcss": "^7.0.2", - "postcss-selector-parser": "^5.0.0-rc.3" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/postcss-dir-pseudo-class/node_modules/cssesc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-dir-pseudo-class/node_modules/postcss-selector-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", - "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", - "dependencies": { - "cssesc": "^2.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-discard-comments": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", - "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", - "dependencies": { - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-discard-duplicates": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", - "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", - "dependencies": { - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-discard-empty": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", - "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", - "dependencies": { - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-discard-overridden": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", - "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", - "dependencies": { - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-double-position-gradients": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz", - "integrity": "sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA==", - "dependencies": { - "postcss": "^7.0.5", - "postcss-values-parser": "^2.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-env-function": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-2.0.2.tgz", - "integrity": "sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw==", - "dependencies": { - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-flexbugs-fixes": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.2.1.tgz", - "integrity": "sha512-9SiofaZ9CWpQWxOwRh1b/r85KD5y7GgvsNt1056k6OYLvWUun0czCvogfJgylC22uJTwW1KzY3Gz65NZRlvoiQ==", - "dependencies": { - "postcss": "^7.0.26" - } - }, - "node_modules/postcss-focus-visible": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz", - "integrity": "sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g==", - "dependencies": { - "postcss": "^7.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-focus-within": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz", - "integrity": "sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w==", - "dependencies": { - "postcss": "^7.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-font-variant": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-4.0.1.tgz", - "integrity": "sha512-I3ADQSTNtLTTd8uxZhtSOrTCQ9G4qUVKPjHiDk0bV75QSxXjVWiJVJ2VLdspGUi9fbW9BcjKJoRvxAH1pckqmA==", - "dependencies": { - "postcss": "^7.0.2" - } - }, - "node_modules/postcss-gap-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz", - "integrity": "sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg==", - "dependencies": { - "postcss": "^7.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-image-set-function": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz", - "integrity": "sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw==", - "dependencies": { - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-initial": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-3.0.4.tgz", - "integrity": "sha512-3RLn6DIpMsK1l5UUy9jxQvoDeUN4gP939tDcKUHD/kM8SGSKbFAnvkpFpj3Bhtz3HGk1jWY5ZNWX6mPta5M9fg==", - "dependencies": { - "postcss": "^7.0.2" - } - }, - "node_modules/postcss-lab-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz", - "integrity": "sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg==", - "dependencies": { - "@csstools/convert-colors": "^1.4.0", - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-load-config": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.2.tgz", - "integrity": "sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw==", - "dependencies": { - "cosmiconfig": "^5.0.0", - "import-cwd": "^2.0.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - } - }, - "node_modules/postcss-load-config/node_modules/cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "dependencies": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-load-config/node_modules/import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", - "dependencies": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-load-config/node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-load-config/node_modules/resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-loader": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", - "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", - "dependencies": { - "loader-utils": "^1.1.0", - "postcss": "^7.0.0", - "postcss-load-config": "^2.0.0", - "schema-utils": "^1.0.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/postcss-loader/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/postcss-loader/node_modules/loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/postcss-loader/node_modules/schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dependencies": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/postcss-logical": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-3.0.0.tgz", - "integrity": "sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA==", - "dependencies": { - "postcss": "^7.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-media-minmax": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz", - "integrity": "sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw==", - "dependencies": { - "postcss": "^7.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-merge-longhand": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", - "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", - "dependencies": { - "css-color-names": "0.0.4", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "stylehacks": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-merge-longhand/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - }, - "node_modules/postcss-merge-rules": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", - "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", - "dependencies": { - "browserslist": "^4.0.0", - "caniuse-api": "^3.0.0", - "cssnano-util-same-parent": "^4.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0", - "vendors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-merge-rules/node_modules/postcss-selector-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", - "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", - "dependencies": { - "dot-prop": "^5.2.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/postcss-minify-font-values": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", - "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", - "dependencies": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-minify-font-values/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - }, - "node_modules/postcss-minify-gradients": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", - "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", - "dependencies": { - "cssnano-util-get-arguments": "^4.0.0", - "is-color-stop": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-minify-gradients/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - }, - "node_modules/postcss-minify-params": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", - "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", - "dependencies": { - "alphanum-sort": "^1.0.0", - "browserslist": "^4.0.0", - "cssnano-util-get-arguments": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "uniqs": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-minify-params/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - }, - "node_modules/postcss-minify-selectors": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", - "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", - "dependencies": { - "alphanum-sort": "^1.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-minify-selectors/node_modules/postcss-selector-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", - "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", - "dependencies": { - "dot-prop": "^5.2.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/postcss-modules-extract-imports": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", - "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", - "dependencies": { - "postcss": "^7.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/postcss-modules-local-by-default": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz", - "integrity": "sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==", - "dependencies": { - "icss-utils": "^4.1.1", - "postcss": "^7.0.32", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/postcss-modules-scope": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz", - "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==", - "dependencies": { - "postcss": "^7.0.6", - "postcss-selector-parser": "^6.0.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/postcss-modules-values": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz", - "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==", - "dependencies": { - "icss-utils": "^4.0.0", - "postcss": "^7.0.6" - } - }, - "node_modules/postcss-nesting": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-7.0.1.tgz", - "integrity": "sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg==", - "dependencies": { - "postcss": "^7.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-normalize": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize/-/postcss-normalize-8.0.1.tgz", - "integrity": "sha512-rt9JMS/m9FHIRroDDBGSMsyW1c0fkvOJPy62ggxSHUldJO7B195TqFMqIf+lY5ezpDcYOV4j86aUp3/XbxzCCQ==", - "dependencies": { - "@csstools/normalize.css": "^10.1.0", - "browserslist": "^4.6.2", - "postcss": "^7.0.17", - "postcss-browser-comments": "^3.0.0", - "sanitize.css": "^10.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/postcss-normalize-charset": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", - "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", - "dependencies": { - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-display-values": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", - "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", - "dependencies": { - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-display-values/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - }, - "node_modules/postcss-normalize-positions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", - "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", - "dependencies": { - "cssnano-util-get-arguments": "^4.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-positions/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - }, - "node_modules/postcss-normalize-repeat-style": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", - "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", - "dependencies": { - "cssnano-util-get-arguments": "^4.0.0", - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-repeat-style/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - }, - "node_modules/postcss-normalize-string": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", - "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", - "dependencies": { - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-string/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - }, - "node_modules/postcss-normalize-timing-functions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", - "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", - "dependencies": { - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-timing-functions/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - }, - "node_modules/postcss-normalize-unicode": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", - "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", - "dependencies": { - "browserslist": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-unicode/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - }, - "node_modules/postcss-normalize-url": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", - "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", - "dependencies": { - "is-absolute-url": "^2.0.0", - "normalize-url": "^3.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-url/node_modules/normalize-url": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", - "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-normalize-url/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - }, - "node_modules/postcss-normalize-whitespace": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", - "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", - "dependencies": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-whitespace/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - }, - "node_modules/postcss-ordered-values": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", - "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", - "dependencies": { - "cssnano-util-get-arguments": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-ordered-values/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - }, - "node_modules/postcss-overflow-shorthand": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz", - "integrity": "sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g==", - "dependencies": { - "postcss": "^7.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-page-break": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-2.0.0.tgz", - "integrity": "sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ==", - "dependencies": { - "postcss": "^7.0.2" - } - }, - "node_modules/postcss-place": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-4.0.1.tgz", - "integrity": "sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg==", - "dependencies": { - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-preset-env": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-6.7.0.tgz", - "integrity": "sha512-eU4/K5xzSFwUFJ8hTdTQzo2RBLbDVt83QZrAvI07TULOkmyQlnYlpwep+2yIK+K+0KlZO4BvFcleOCCcUtwchg==", - "dependencies": { - "autoprefixer": "^9.6.1", - "browserslist": "^4.6.4", - "caniuse-lite": "^1.0.30000981", - "css-blank-pseudo": "^0.1.4", - "css-has-pseudo": "^0.10.0", - "css-prefers-color-scheme": "^3.1.1", - "cssdb": "^4.4.0", - "postcss": "^7.0.17", - "postcss-attribute-case-insensitive": "^4.0.1", - "postcss-color-functional-notation": "^2.0.1", - "postcss-color-gray": "^5.0.0", - "postcss-color-hex-alpha": "^5.0.3", - "postcss-color-mod-function": "^3.0.3", - "postcss-color-rebeccapurple": "^4.0.1", - "postcss-custom-media": "^7.0.8", - "postcss-custom-properties": "^8.0.11", - "postcss-custom-selectors": "^5.1.2", - "postcss-dir-pseudo-class": "^5.0.0", - "postcss-double-position-gradients": "^1.0.0", - "postcss-env-function": "^2.0.2", - "postcss-focus-visible": "^4.0.0", - "postcss-focus-within": "^3.0.0", - "postcss-font-variant": "^4.0.0", - "postcss-gap-properties": "^2.0.0", - "postcss-image-set-function": "^3.0.1", - "postcss-initial": "^3.0.0", - "postcss-lab-function": "^2.0.1", - "postcss-logical": "^3.0.0", - "postcss-media-minmax": "^4.0.0", - "postcss-nesting": "^7.0.0", - "postcss-overflow-shorthand": "^2.0.0", - "postcss-page-break": "^2.0.0", - "postcss-place": "^4.0.1", - "postcss-pseudo-class-any-link": "^6.0.0", - "postcss-replace-overflow-wrap": "^3.0.0", - "postcss-selector-matches": "^4.0.0", - "postcss-selector-not": "^4.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-pseudo-class-any-link": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz", - "integrity": "sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew==", - "dependencies": { - "postcss": "^7.0.2", - "postcss-selector-parser": "^5.0.0-rc.3" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-pseudo-class-any-link/node_modules/cssesc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-pseudo-class-any-link/node_modules/postcss-selector-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", - "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", - "dependencies": { - "cssesc": "^2.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-reduce-initial": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", - "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", - "dependencies": { - "browserslist": "^4.0.0", - "caniuse-api": "^3.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-reduce-transforms": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", - "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", - "dependencies": { - "cssnano-util-get-match": "^4.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-reduce-transforms/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - }, - "node_modules/postcss-replace-overflow-wrap": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz", - "integrity": "sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw==", - "dependencies": { - "postcss": "^7.0.2" - } - }, - "node_modules/postcss-safe-parser": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-5.0.2.tgz", - "integrity": "sha512-jDUfCPJbKOABhwpUKcqCVbbXiloe/QXMcbJ6Iipf3sDIihEzTqRCeMBfRaOHxhBuTYqtASrI1KJWxzztZU4qUQ==", - "dependencies": { - "postcss": "^8.1.0" - }, - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - } - }, - "node_modules/postcss-safe-parser/node_modules/postcss": { - "version": "8.4.7", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.7.tgz", - "integrity": "sha512-L9Ye3r6hkkCeOETQX6iOaWZgjp3LL6Lpqm6EtgbKrgqGGteRMNb9vzBfRL96YOSu8o7x3MfIH9Mo5cPJFGrW6A==", - "dependencies": { - "nanoid": "^3.3.1", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - } - }, - "node_modules/postcss-selector-matches": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz", - "integrity": "sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww==", - "dependencies": { - "balanced-match": "^1.0.0", - "postcss": "^7.0.2" - } - }, - "node_modules/postcss-selector-not": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-4.0.1.tgz", - "integrity": "sha512-YolvBgInEK5/79C+bdFMyzqTg6pkYqDbzZIST/PDMqa/o3qtXenD05apBG2jLgT0/BQ77d4U2UK12jWpilqMAQ==", - "dependencies": { - "balanced-match": "^1.0.0", - "postcss": "^7.0.2" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz", - "integrity": "sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ==", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-svgo": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.3.tgz", - "integrity": "sha512-NoRbrcMWTtUghzuKSoIm6XV+sJdvZ7GZSc3wdBN0W19FTtp2ko8NqLsgoh/m9CzNhU3KLPvQmjIwtaNFkaFTvw==", - "dependencies": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "svgo": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-svgo/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - }, - "node_modules/postcss-unique-selectors": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", - "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", - "dependencies": { - "alphanum-sort": "^1.0.0", - "postcss": "^7.0.0", - "uniqs": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" - }, - "node_modules/postcss-values-parser": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz", - "integrity": "sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg==", - "dependencies": { - "flatten": "^1.0.2", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - }, - "engines": { - "node": ">=6.14.4" - } - }, - "node_modules/postcss/node_modules/picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" - }, - "node_modules/postcss/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/preact": { - "version": "10.5.14", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.5.14.tgz", - "integrity": "sha512-KojoltCrshZ099ksUZ2OQKfbH66uquFoxHSbnwKbTJHeQNvx42EmC7wQVWNuDt6vC5s3nudRHFtKbpY4ijKlaQ==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pretty-error": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz", - "integrity": "sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw==", - "dependencies": { - "lodash": "^4.17.20", - "renderkid": "^2.0.4" - } - }, - "node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/pretty-format/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/pretty-format/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/pretty-format/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" - }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/promise": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz", - "integrity": "sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q==", - "dependencies": { - "asap": "~2.0.6" - } - }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" - }, - "node_modules/prompts": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz", - "integrity": "sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ==", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/property-expr": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz", - "integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" - }, - "node_modules/psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" - }, - "node_modules/public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "dependencies": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/public-encrypt/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dependencies": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - } - }, - "node_modules/pumpify/node_modules/pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "engines": { - "node": ">=6" - } - }, - "node_modules/q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" - } - }, - "node_modules/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/query-string": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", - "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", - "dependencies": { - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/querystring": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz", - "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==", - "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/raf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", - "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", - "dependencies": { - "performance-now": "^2.1.0" - } - }, - "node_modules/ramda": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.28.0.tgz", - "integrity": "sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dependencies": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", - "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "1.8.1", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/react": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.1.tgz", - "integrity": "sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-app-polyfill": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-2.0.0.tgz", - "integrity": "sha512-0sF4ny9v/B7s6aoehwze9vJNWcmCemAUYBVasscVr92+UYiEqDXOxfKjXN685mDaMRNF3WdhHQs76oTODMocFA==", - "dependencies": { - "core-js": "^3.6.5", - "object-assign": "^4.1.1", - "promise": "^8.1.0", - "raf": "^3.4.1", - "regenerator-runtime": "^0.13.7", - "whatwg-fetch": "^3.4.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/react-dev-utils": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz", - "integrity": "sha512-dx0LvIGHcOPtKbeiSUM4jqpBl3TcY7CDjZdfOIcKeznE7BWr9dg0iPG90G5yfVQ+p/rGNMXdbfStvzQZEVEi4A==", - "dependencies": { - "@babel/code-frame": "7.10.4", - "address": "1.1.2", - "browserslist": "4.14.2", - "chalk": "2.4.2", - "cross-spawn": "7.0.3", - "detect-port-alt": "1.1.6", - "escape-string-regexp": "2.0.0", - "filesize": "6.1.0", - "find-up": "4.1.0", - "fork-ts-checker-webpack-plugin": "4.1.6", - "global-modules": "2.0.0", - "globby": "11.0.1", - "gzip-size": "5.1.1", - "immer": "8.0.1", - "is-root": "2.1.0", - "loader-utils": "2.0.0", - "open": "^7.0.2", - "pkg-up": "3.1.0", - "prompts": "2.4.0", - "react-error-overlay": "^6.0.9", - "recursive-readdir": "2.2.2", - "shell-quote": "1.7.2", - "strip-ansi": "6.0.0", - "text-table": "0.2.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/react-dev-utils/node_modules/@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/react-dev-utils/node_modules/browserslist": { - "version": "4.14.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.2.tgz", - "integrity": "sha512-HI4lPveGKUR0x2StIz+2FXfDk9SfVMrxn6PLh1JeGUwcuoDkdKZebWiyLRJ68iIPDpMI4JLVDf7S7XzslgWOhw==", - "dependencies": { - "caniuse-lite": "^1.0.30001125", - "electron-to-chromium": "^1.3.564", - "escalade": "^3.0.2", - "node-releases": "^1.1.61" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - }, - "funding": { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - }, - "node_modules/react-dev-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/react-dev-utils/node_modules/globby": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", - "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/react-dev-utils/node_modules/node-releases": { - "version": "1.1.77", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.77.tgz", - "integrity": "sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ==" - }, - "node_modules/react-dev-utils/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/react-dom": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.1.tgz", - "integrity": "sha512-6eV150oJZ9U2t9svnsspTMrWNyHc6chX0KzDeAOXftRa8bNeOKTTfCJ7KorIwenkHd2xqVTBTCZd79yk/lx/Ug==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.1" - }, - "peerDependencies": { - "react": "17.0.1" - } - }, - "node_modules/react-error-overlay": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.10.tgz", - "integrity": "sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA==" - }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/react-markdown": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-5.0.3.tgz", - "integrity": "sha512-jDWOc1AvWn0WahpjW6NK64mtx6cwjM4iSsLHJPNBqoAgGOVoIdJMqaKX4++plhOtdd4JksdqzlDibgPx6B/M2w==", - "dependencies": { - "@types/mdast": "^3.0.3", - "@types/unist": "^2.0.3", - "html-to-react": "^1.3.4", - "mdast-add-list-metadata": "1.0.1", - "prop-types": "^15.7.2", - "react-is": "^16.8.6", - "remark-parse": "^9.0.0", - "unified": "^9.0.0", - "unist-util-visit": "^2.0.0", - "xtend": "^4.0.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - }, - "peerDependencies": { - "@types/react": ">=16", - "react": ">=16" - } - }, - "node_modules/react-refresh": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", - "integrity": "sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-router": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", - "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", - "dependencies": { - "@babel/runtime": "^7.1.2", - "history": "^4.9.0", - "hoist-non-react-statics": "^3.1.0", - "loose-envify": "^1.3.1", - "mini-create-react-context": "^0.4.0", - "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.2", - "react-is": "^16.6.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - }, - "peerDependencies": { - "react": ">=15" - } - }, - "node_modules/react-router-dom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", - "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", - "dependencies": { - "@babel/runtime": "^7.1.2", - "history": "^4.9.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.2", - "react-router": "5.2.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - }, - "peerDependencies": { - "react": ">=15" - } - }, - "node_modules/react-router/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "node_modules/react-router/node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dependencies": { - "isarray": "0.0.1" - } - }, - "node_modules/react-scripts": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-4.0.3.tgz", - "integrity": "sha512-S5eO4vjUzUisvkIPB7jVsKtuH2HhWcASREYWHAQ1FP5HyCv3xgn+wpILAEWkmy+A+tTNbSZClhxjT3qz6g4L1A==", - "dependencies": { - "@babel/core": "7.12.3", - "@pmmmwh/react-refresh-webpack-plugin": "0.4.3", - "@svgr/webpack": "5.5.0", - "@typescript-eslint/eslint-plugin": "^4.5.0", - "@typescript-eslint/parser": "^4.5.0", - "babel-eslint": "^10.1.0", - "babel-jest": "^26.6.0", - "babel-loader": "8.1.0", - "babel-plugin-named-asset-import": "^0.3.7", - "babel-preset-react-app": "^10.0.0", - "bfj": "^7.0.2", - "camelcase": "^6.1.0", - "case-sensitive-paths-webpack-plugin": "2.3.0", - "css-loader": "4.3.0", - "dotenv": "8.2.0", - "dotenv-expand": "5.1.0", - "eslint": "^7.11.0", - "eslint-config-react-app": "^6.0.0", - "eslint-plugin-flowtype": "^5.2.0", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-jest": "^24.1.0", - "eslint-plugin-jsx-a11y": "^6.3.1", - "eslint-plugin-react": "^7.21.5", - "eslint-plugin-react-hooks": "^4.2.0", - "eslint-plugin-testing-library": "^3.9.2", - "eslint-webpack-plugin": "^2.5.2", - "file-loader": "6.1.1", - "fs-extra": "^9.0.1", - "html-webpack-plugin": "4.5.0", - "identity-obj-proxy": "3.0.0", - "jest": "26.6.0", - "jest-circus": "26.6.0", - "jest-resolve": "26.6.0", - "jest-watch-typeahead": "0.6.1", - "mini-css-extract-plugin": "0.11.3", - "optimize-css-assets-webpack-plugin": "5.0.4", - "pnp-webpack-plugin": "1.6.4", - "postcss-flexbugs-fixes": "4.2.1", - "postcss-loader": "3.0.0", - "postcss-normalize": "8.0.1", - "postcss-preset-env": "6.7.0", - "postcss-safe-parser": "5.0.2", - "prompts": "2.4.0", - "react-app-polyfill": "^2.0.0", - "react-dev-utils": "^11.0.3", - "react-refresh": "^0.8.3", - "resolve": "1.18.1", - "resolve-url-loader": "^3.1.2", - "sass-loader": "^10.0.5", - "semver": "7.3.2", - "style-loader": "1.3.0", - "terser-webpack-plugin": "4.2.3", - "ts-pnp": "1.2.0", - "url-loader": "4.1.1", - "webpack": "4.44.2", - "webpack-dev-server": "3.11.1", - "webpack-manifest-plugin": "2.2.0", - "workbox-webpack-plugin": "5.1.4" - }, - "bin": { - "react-scripts": "bin/react-scripts.js" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.1.3" - }, - "peerDependencies": { - "react": ">= 16", - "typescript": "^3.2.1 || ^4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/react-scripts/node_modules/@babel/core": { - "version": "7.12.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.3.tgz", - "integrity": "sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g==", - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.1", - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helpers": "^7.12.1", - "@babel/parser": "^7.12.3", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.2", - "lodash": "^4.17.19", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/react-scripts/node_modules/@babel/core/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/react-scripts/node_modules/resolve": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", - "integrity": "sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==", - "dependencies": { - "is-core-module": "^2.0.0", - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/react-transition-group": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", - "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", - "dependencies": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": ">=16.6.0", - "react-dom": ">=16.6.0" - } - }, - "node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readable-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "optional": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/recursive-readdir": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", - "integrity": "sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==", - "dependencies": { - "minimatch": "3.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/recursive-readdir/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", - "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" - }, - "node_modules/regenerator-transform": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", - "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regex-parser": { - "version": "2.2.11", - "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", - "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==" - }, - "node_modules/regexp.prototype.flags": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz", - "integrity": "sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/regexpu-core": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz", - "integrity": "sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw==", - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.0.1", - "regjsgen": "^0.6.0", - "regjsparser": "^0.8.2", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsgen": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", - "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==" - }, - "node_modules/regjsparser": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", - "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/remark-parse": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-9.0.0.tgz", - "integrity": "sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==", - "dependencies": { - "mdast-util-from-markdown": "^0.8.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" - }, - "node_modules/renderkid": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.7.tgz", - "integrity": "sha512-oCcFyxaMrKsKcTY59qnCAtmDVSLfPbrv6A3tVbPdFMMrv5jaK10V6m40cKsoPNhAqN6rmHW9sswW4o3ruSrwUQ==", - "dependencies": { - "css-select": "^4.1.3", - "dom-converter": "^0.2.0", - "htmlparser2": "^6.1.0", - "lodash": "^4.17.21", - "strip-ansi": "^3.0.1" - } - }, - "node_modules/renderkid/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/renderkid/node_modules/htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "node_modules/renderkid/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "engines": { - "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==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" - }, - "node_modules/resolve": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", - "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", - "dependencies": { - "is-core-module": "^2.8.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-pathname": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" - }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "deprecated": "https://github.com/lydell/resolve-url#deprecated" - }, - "node_modules/resolve-url-loader": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-3.1.5.tgz", - "integrity": "sha512-mgFMCmrV/tA4738EsFmPFE5/MaqSgUMe8LK971kVEKA/RrNVb7+VqFsg/qmKyythf34eyq476qIobP/gfFBGSQ==", - "dependencies": { - "adjust-sourcemap-loader": "3.0.0", - "camelcase": "5.3.1", - "compose-function": "3.0.3", - "convert-source-map": "1.7.0", - "es6-iterator": "2.0.3", - "loader-utils": "^1.2.3", - "postcss": "7.0.36", - "rework": "1.0.1", - "rework-visit": "1.0.0", - "source-map": "0.6.1" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/resolve-url-loader/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/resolve-url-loader/node_modules/convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/resolve-url-loader/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/resolve-url-loader/node_modules/loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/resolve-url-loader/node_modules/postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - } - }, - "node_modules/resolve-url-loader/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-url-loader/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "engines": { - "node": ">=0.12" - } - }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", - "engines": { - "node": ">= 4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rework": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rework/-/rework-1.0.1.tgz", - "integrity": "sha1-MIBqhBNCtUUQqkEQhQzUhTQUSqc=", - "dependencies": { - "convert-source-map": "^0.3.3", - "css": "^2.0.0" - } - }, - "node_modules/rework-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rework-visit/-/rework-visit-1.0.0.tgz", - "integrity": "sha1-mUWygD8hni96ygCtuLyfZA+ELJo=" - }, - "node_modules/rework/node_modules/convert-source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-0.3.5.tgz", - "integrity": "sha1-8dgClQr33SYxof6+BZZVDIarMZA=" - }, - "node_modules/rgb-regex": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", - "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=" - }, - "node_modules/rgba-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", - "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=" - }, - "node_modules/rifm": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/rifm/-/rifm-0.7.0.tgz", - "integrity": "sha512-DSOJTWHD67860I5ojetXdEQRIBvF6YcpNe53j0vn1vp9EUb9N80EiZTxgP+FkDKorWC8PZw052kTF4C1GOivCQ==", - "dependencies": { - "@babel/runtime": "^7.3.1" - }, - "peerDependencies": { - "react": ">=16.8" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "node_modules/rollup": { - "version": "1.32.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.32.1.tgz", - "integrity": "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A==", - "dependencies": { - "@types/estree": "*", - "@types/node": "*", - "acorn": "^7.1.0" - }, - "bin": { - "rollup": "dist/bin/rollup" - } - }, - "node_modules/rollup-plugin-babel": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-babel/-/rollup-plugin-babel-4.4.0.tgz", - "integrity": "sha512-Lek/TYp1+7g7I+uMfJnnSJ7YWoD58ajo6Oarhlex7lvUce+RCKRuGRSgztDO3/MF/PuGKmUL5iTHKf208UNszw==", - "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-babel.", - "dependencies": { - "@babel/helper-module-imports": "^7.0.0", - "rollup-pluginutils": "^2.8.1" - }, - "peerDependencies": { - "@babel/core": "7 || ^7.0.0-rc.2", - "rollup": ">=0.60.0 <3" - } - }, - "node_modules/rollup-plugin-terser": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-5.3.1.tgz", - "integrity": "sha512-1pkwkervMJQGFYvM9nscrUoncPwiKR/K+bHdjv6PFgRo3cgPHoRT83y2Aa3GvINj4539S15t/tpFPb775TDs6w==", - "dependencies": { - "@babel/code-frame": "^7.5.5", - "jest-worker": "^24.9.0", - "rollup-pluginutils": "^2.8.2", - "serialize-javascript": "^4.0.0", - "terser": "^4.6.2" - }, - "peerDependencies": { - "rollup": ">=0.66.0 <3" - } - }, - "node_modules/rollup-plugin-terser/node_modules/jest-worker": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", - "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", - "dependencies": { - "merge-stream": "^2.0.0", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/rollup-plugin-terser/node_modules/serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/rollup-plugin-terser/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/rollup-pluginutils": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", - "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", - "dependencies": { - "estree-walker": "^0.6.1" - } - }, - "node_modules/rollup-pluginutils/node_modules/estree-walker": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", - "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==" - }, - "node_modules/rsvp": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", - "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", - "engines": { - "node": "6.* || >= 7.*" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/run-queue": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", - "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", - "dependencies": { - "aproba": "^1.1.1" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dependencies": { - "ret": "~0.1.10" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/sane": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", - "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", - "deprecated": "some dependency vulnerabilities fixed, support for node < 10 dropped, and newer ECMAScript syntax/features added", - "dependencies": { - "@cnakazawa/watch": "^1.0.3", - "anymatch": "^2.0.0", - "capture-exit": "^2.0.0", - "exec-sh": "^0.3.2", - "execa": "^1.0.0", - "fb-watchman": "^2.0.0", - "micromatch": "^3.1.4", - "minimist": "^1.1.1", - "walker": "~1.0.5" - }, - "bin": { - "sane": "src/cli.js" - }, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/sane/node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/sane/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/sane/node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/sane/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/sane/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "node_modules/sane/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/sane/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "engines": { - "node": ">=4" - } - }, - "node_modules/sane/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/sane/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/sanitize.css": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-10.0.0.tgz", - "integrity": "sha512-vTxrZz4dX5W86M6oVWVdOVe72ZiPs41Oi7Z6Km4W5Turyz28mrXSJhhEBZoRtzJWIv3833WKVwLSDWWkEfupMg==" - }, - "node_modules/sass-loader": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-10.2.1.tgz", - "integrity": "sha512-RRvWl+3K2LSMezIsd008ErK4rk6CulIMSwrcc2aZvjymUgKo/vjXGp1rSWmfTUX7bblEOz8tst4wBwWtCGBqKA==", - "dependencies": { - "klona": "^2.0.4", - "loader-utils": "^2.0.0", - "neo-async": "^2.6.2", - "schema-utils": "^3.0.0", - "semver": "^7.3.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "fibers": ">= 3.1.0", - "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0", - "sass": "^1.3.0", - "webpack": "^4.36.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "fibers": { - "optional": true - }, - "node-sass": { - "optional": true - }, - "sass": { - "optional": true - } - } - }, - "node_modules/sass-loader/node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "node_modules/schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", - "dependencies": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 8.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=" - }, - "node_modules/selfsigned": { - "version": "1.10.14", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.14.tgz", - "integrity": "sha512-lkjaiAye+wBZDCBsu5BGi0XiLRxeUlsGod5ZP924CRSEoGuZAw/f7y9RKu28rwTfiHVhdavhB0qH0INV6P1lEA==", - "dependencies": { - "node-forge": "^0.10.0" - } - }, - "node_modules/semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", - "dependencies": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "1.8.1", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-index/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "node_modules/serve-index/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" - }, - "node_modules/serve-static": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" - } - }, - "node_modules/shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", - "peer": true - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" - } - }, - "node_modules/shell-quote": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", - "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==" - }, - "node_modules/shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "optional": true - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/simple-swizzle/node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/slice-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dependencies": { - "kind-of": "^3.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "node_modules/snapdragon-util/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/snapdragon/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "node_modules/snapdragon/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/sockjs": { - "version": "0.3.24", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", - "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", - "dependencies": { - "faye-websocket": "^0.11.3", - "uuid": "^8.3.2", - "websocket-driver": "^0.7.4" - } - }, - "node_modules/sockjs-client": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.5.2.tgz", - "integrity": "sha512-ZzRxPBISQE7RpzlH4tKJMQbHM9pabHluk0WBaxAQ+wm/UieeBVBou0p4wVnSQGN9QmpAZygQ0cDIypWuqOFmFQ==", - "dependencies": { - "debug": "^3.2.6", - "eventsource": "^1.0.7", - "faye-websocket": "^0.11.3", - "inherits": "^2.0.4", - "json3": "^3.3.3", - "url-parse": "^1.5.3" - } - }, - "node_modules/sockjs-client/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/sort-keys": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", - "dependencies": { - "is-plain-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==" - }, - "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "deprecated": "See https://github.com/lydell/source-map-url#deprecated" - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" - }, - "node_modules/spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", - "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==" - }, - "node_modules/spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "dependencies": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "dependencies": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, - "node_modules/spdy-transport/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dependencies": { - "extend-shallow": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" - }, - "node_modules/ssri": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", - "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" - }, - "node_modules/stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/stackframe": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.1.tgz", - "integrity": "sha512-h88QkzREN/hy8eRdyNhhsO7RSJ5oyTqxxmmn0dzBIMUclZsjpfmrsg81vp8mjjAs2vAZ72nyWxRUwSwmh0e4xg==" - }, - "node_modules/static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "node_modules/static-extend/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/stream-browserify": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", - "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", - "dependencies": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" - } - }, - "node_modules/stream-each": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", - "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", - "dependencies": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/stream-http": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", - "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", - "dependencies": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" - } - }, - "node_modules/stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" - }, - "node_modules/strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", - "engines": { - "node": ">=0.10.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==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-natural-compare": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz", - "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==" - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/string.prototype.matchall": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.6.tgz", - "integrity": "sha512-6WgDX8HmQqvEd7J+G6VtAahhsQIssiZ8zl7zKh1VDMFyL3hRTJP4FTNA3RbIp2TOQ9AYNDcc7e3fH0Qbup+DBg==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", - "get-intrinsic": "^1.1.1", - "has-symbols": "^1.0.2", - "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.3.1", - "side-channel": "^1.0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", - "dependencies": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-comments": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-1.0.2.tgz", - "integrity": "sha512-kL97alc47hoyIQSV165tTt9rG5dn4w1dNnBhOQ3bOU1Nc1hel09jnXANaHJ7vzHLd4Ju8kseDGzlev96pghLFw==", - "dependencies": { - "babel-extract-comments": "^1.0.0", - "babel-plugin-transform-object-rest-spread": "^6.26.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/style-loader": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.3.0.tgz", - "integrity": "sha512-V7TCORko8rs9rIqkSrlMfkqA63DfoGBBJmK1kKGCcSi+BWb4cqz0SRsnp4l6rU5iwOEd0/2ePv68SV22VXon4Q==", - "dependencies": { - "loader-utils": "^2.0.0", - "schema-utils": "^2.7.0" - }, - "engines": { - "node": ">= 8.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/styled-components": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.2.1.tgz", - "integrity": "sha512-sBdgLWrCFTKtmZm/9x7jkIabjFNVzCUeKfoQsM6R3saImkUnjx0QYdLwJHBjY9ifEcmjDamJDVfknWm1yxZPxQ==", - "peer": true, - "dependencies": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/traverse": "^7.4.5", - "@emotion/is-prop-valid": "^0.8.8", - "@emotion/stylis": "^0.8.4", - "@emotion/unitless": "^0.7.4", - "babel-plugin-styled-components": ">= 1", - "css-to-react-native": "^3.0.0", - "hoist-non-react-statics": "^3.0.0", - "shallowequal": "^1.1.0", - "supports-color": "^5.5.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/styled-components" - }, - "peerDependencies": { - "react": ">= 16.8.0", - "react-dom": ">= 16.8.0", - "react-is": ">= 16.8.0" - } - }, - "node_modules/stylehacks": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", - "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", - "dependencies": { - "browserslist": "^4.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/stylehacks/node_modules/postcss-selector-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", - "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", - "dependencies": { - "dot-prop": "^5.2.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-hyperlinks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", - "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/svg-parser": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", - "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" - }, - "node_modules/svgo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", - "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", - "dependencies": { - "chalk": "^2.4.1", - "coa": "^2.0.2", - "css-select": "^2.0.0", - "css-select-base-adapter": "^0.1.1", - "css-tree": "1.0.0-alpha.37", - "csso": "^4.0.2", - "js-yaml": "^3.13.1", - "mkdirp": "~0.5.1", - "object.values": "^1.1.0", - "sax": "~1.2.4", - "stable": "^0.1.8", - "unquote": "~1.1.1", - "util.promisify": "~1.0.0" - }, - "bin": { - "svgo": "bin/svgo" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/svgo/node_modules/css-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", - "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^3.2.1", - "domutils": "^1.7.0", - "nth-check": "^1.0.2" - } - }, - "node_modules/svgo/node_modules/css-what": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", - "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/svgo/node_modules/dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", - "dependencies": { - "domelementtype": "^2.0.1", - "entities": "^2.0.0" - } - }, - "node_modules/svgo/node_modules/domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "dependencies": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "node_modules/svgo/node_modules/domutils/node_modules/domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" - }, - "node_modules/svgo/node_modules/nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "dependencies": { - "boolbase": "~1.0.0" - } - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" - }, - "node_modules/table": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", - "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table/node_modules/ajv": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", - "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", - "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/table/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==" - }, - "node_modules/tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/tar": { - "version": "6.1.11", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", - "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/tar/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/temp-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", - "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=", - "engines": { - "node": ">=4" - } - }, - "node_modules/tempy": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.3.0.tgz", - "integrity": "sha512-WrH/pui8YCwmeiAoxV+lpRH9HpRtgBhSR2ViBPgpGb/wnYDzp21R4MN45fsCGvLROvY67o3byhJRYRONJyImVQ==", - "dependencies": { - "temp-dir": "^1.0.0", - "type-fest": "^0.3.1", - "unique-string": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tempy/node_modules/type-fest": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", - "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/terser": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz", - "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==", - "dependencies": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-4.2.3.tgz", - "integrity": "sha512-jTgXh40RnvOrLQNgIkwEKnQ8rmHjHK4u+6UBEi+W+FPmvb+uo+chJXntKe7/3lW5mNysgSWD60KyesnhW8D6MQ==", - "dependencies": { - "cacache": "^15.0.5", - "find-cache-dir": "^3.3.1", - "jest-worker": "^26.5.0", - "p-limit": "^3.0.2", - "schema-utils": "^3.0.0", - "serialize-javascript": "^5.0.1", - "source-map": "^0.6.1", - "terser": "^5.3.4", - "webpack-sources": "^1.4.3" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "node_modules/terser-webpack-plugin/node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, - "node_modules/terser-webpack-plugin/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/terser-webpack-plugin/node_modules/jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/terser-webpack-plugin/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/terser-webpack-plugin/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/terser-webpack-plugin/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/terser-webpack-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/terser-webpack-plugin/node_modules/terser": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz", - "integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==", - "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "node_modules/terser/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" - }, - "node_modules/throat": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==" - }, - "node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" - }, - "node_modules/timers-browserify": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", - "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", - "dependencies": { - "setimmediate": "^1.0.4" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/timsort": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", - "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" - }, - "node_modules/tiny-invariant": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", - "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" - }, - "node_modules/tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==" - }, - "node_modules/to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=" - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "engines": { - "node": ">=4" - } - }, - "node_modules/to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-object-path/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "node_modules/to-object-path/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/toposort": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", - "integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=" - }, - "node_modules/tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.1.2" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tough-cookie/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/trough": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", - "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/tryer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", - "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==" - }, - "node_modules/ts-pnp": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", - "integrity": "sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==", - "engines": { - "node": ">=6" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/tsconfig-paths": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz", - "integrity": "sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==", - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.0", - "strip-bom": "^3.0.0" - } - }, - "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/tsconfig-paths/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "engines": { - "node": ">=4" - } - }, - "node_modules/tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=" - }, - "node_modules/type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "optional": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "dependencies": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", - "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/unified": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.2.tgz", - "integrity": "sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==", - "dependencies": { - "bail": "^1.0.0", - "extend": "^3.0.0", - "is-buffer": "^2.0.0", - "is-plain-obj": "^2.0.0", - "trough": "^1.0.0", - "vfile": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unified/node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/union-value/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/uniq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=" - }, - "node_modules/uniqs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", - "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=" - }, - "node_modules/unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "dependencies": { - "unique-slug": "^2.0.0" - } - }, - "node_modules/unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "dependencies": { - "imurmurhash": "^0.1.4" - } - }, - "node_modules/unique-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", - "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", - "dependencies": { - "crypto-random-string": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unist-util-is": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", - "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", - "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", - "dependencies": { - "@types/unist": "^2.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", - "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0", - "unist-util-visit-parents": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-1.1.2.tgz", - "integrity": "sha512-yvo+MMLjEwdc3RhhPYSximset7rwjMrdt9E41Smmvg25UQIenzrN83cRnF1JMzoMi9zZOQeYXHSDf7p+IQkW3Q==" - }, - "node_modules/unist-util-visit/node_modules/unist-util-visit-parents": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", - "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/unquote": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", - "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=" - }, - "node_modules/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "engines": { - "node": ">=4", - "yarn": "*" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "deprecated": "Please see https://github.com/lydell/urix#deprecated" - }, - "node_modules/url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dependencies": { - "punycode": "1.3.2", - "querystring": "0.2.0" - } - }, - "node_modules/url-loader": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", - "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", - "dependencies": { - "loader-utils": "^2.0.0", - "mime-types": "^2.1.27", - "schema-utils": "^3.0.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "file-loader": "*", - "webpack": "^4.0.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "file-loader": { - "optional": true - } - } - }, - "node_modules/url-loader/node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/url/node_modules/punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" - }, - "node_modules/url/node_modules/querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/util": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", - "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", - "dependencies": { - "inherits": "2.0.3" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "node_modules/util.promisify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", - "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", - "dependencies": { - "define-properties": "^1.1.2", - "object.getownpropertydescriptors": "^2.0.3" - } - }, - "node_modules/util/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "node_modules/utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==" - }, - "node_modules/v8-to-istanbul": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz", - "integrity": "sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/v8-to-istanbul/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/value-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vendors": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", - "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/vfile": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.1.tgz", - "integrity": "sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==", - "dependencies": { - "@types/unist": "^2.0.0", - "is-buffer": "^2.0.0", - "unist-util-stringify-position": "^2.0.0", - "vfile-message": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-message": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", - "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==", - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-stringify-position": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vm-browserify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==" - }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dependencies": { - "browser-process-hrtime": "^1.0.0" - } - }, - "node_modules/w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dependencies": { - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/watchpack": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", - "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", - "dependencies": { - "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0" - }, - "optionalDependencies": { - "chokidar": "^3.4.1", - "watchpack-chokidar2": "^2.0.1" - } - }, - "node_modules/watchpack-chokidar2": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", - "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", - "optional": true, - "dependencies": { - "chokidar": "^2.1.8" - } - }, - "node_modules/watchpack-chokidar2/node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "optional": true, - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/watchpack-chokidar2/node_modules/anymatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "optional": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "optional": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies", - "optional": true, - "dependencies": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - }, - "optionalDependencies": { - "fsevents": "^1.2.7" - } - }, - "node_modules/watchpack-chokidar2/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "optional": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", - "optional": true, - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/glob-parent/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "optional": true, - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "optional": true, - "dependencies": { - "binary-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "optional": true - }, - "node_modules/watchpack-chokidar2/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "optional": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "optional": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "optional": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "optional": true, - "dependencies": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/watchpack-chokidar2/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "optional": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "dependencies": { - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/web-vitals": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-1.1.2.tgz", - "integrity": "sha512-PFMKIY+bRSXlMxVAQ+m2aw9c/ioUYfDgrYot0YUa+/xa0sakubWhSDyxAKwzymvXVdF4CZI71g06W+mqhzu6ig==" - }, - "node_modules/webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "engines": { - "node": ">=10.4" - } - }, - "node_modules/webpack": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.2.tgz", - "integrity": "sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q==", - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/wasm-edit": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "acorn": "^6.4.1", - "ajv": "^6.10.2", - "ajv-keywords": "^3.4.1", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^4.3.0", - "eslint-scope": "^4.0.3", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^2.4.0", - "loader-utils": "^1.2.3", - "memory-fs": "^0.4.1", - "micromatch": "^3.1.10", - "mkdirp": "^0.5.3", - "neo-async": "^2.6.1", - "node-libs-browser": "^2.2.1", - "schema-utils": "^1.0.0", - "tapable": "^1.1.3", - "terser-webpack-plugin": "^1.4.3", - "watchpack": "^1.7.4", - "webpack-sources": "^1.4.1" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=6.11.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - }, - "webpack-command": { - "optional": true - } - } - }, - "node_modules/webpack-dev-middleware": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz", - "integrity": "sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ==", - "dependencies": { - "memory-fs": "^0.4.1", - "mime": "^2.4.4", - "mkdirp": "^0.5.1", - "range-parser": "^1.2.1", - "webpack-log": "^2.0.0" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/webpack-dev-middleware/node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/webpack-dev-server": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.1.tgz", - "integrity": "sha512-u4R3mRzZkbxQVa+MBWi2uVpB5W59H3ekZAJsQlKUTdl7Elcah2EhygTPLmeFXybQkf9i2+L0kn7ik9SnXa6ihQ==", - "dependencies": { - "ansi-html": "0.0.7", - "bonjour": "^3.5.0", - "chokidar": "^2.1.8", - "compression": "^1.7.4", - "connect-history-api-fallback": "^1.6.0", - "debug": "^4.1.1", - "del": "^4.1.1", - "express": "^4.17.1", - "html-entities": "^1.3.1", - "http-proxy-middleware": "0.19.1", - "import-local": "^2.0.0", - "internal-ip": "^4.3.0", - "ip": "^1.1.5", - "is-absolute-url": "^3.0.3", - "killable": "^1.0.1", - "loglevel": "^1.6.8", - "opn": "^5.5.0", - "p-retry": "^3.0.1", - "portfinder": "^1.0.26", - "schema-utils": "^1.0.0", - "selfsigned": "^1.10.8", - "semver": "^6.3.0", - "serve-index": "^1.9.1", - "sockjs": "^0.3.21", - "sockjs-client": "^1.5.0", - "spdy": "^4.0.2", - "strip-ansi": "^3.0.1", - "supports-color": "^6.1.0", - "url": "^0.11.0", - "webpack-dev-middleware": "^3.7.2", - "webpack-log": "^2.0.0", - "ws": "^6.2.1", - "yargs": "^13.3.2" - }, - "bin": { - "webpack-dev-server": "bin/webpack-dev-server.js" - }, - "engines": { - "node": ">= 6.11.5" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-dev-server/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/webpack-dev-server/node_modules/anymatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies", - "dependencies": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - }, - "optionalDependencies": { - "fsevents": "^1.2.7" - } - }, - "node_modules/webpack-dev-server/node_modules/cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dependencies": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - } - }, - "node_modules/webpack-dev-server/node_modules/cliui/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/cliui/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" - }, - "node_modules/webpack-dev-server/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "node_modules/webpack-dev-server/node_modules/glob-parent/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/import-local": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", - "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", - "dependencies": { - "pkg-dir": "^3.0.0", - "resolve-cwd": "^2.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/is-absolute-url": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", - "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/webpack-dev-server/node_modules/is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dependencies": { - "binary-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "node_modules/webpack-dev-server/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "engines": { - "node": ">=4" - } - }, - "node_modules/webpack-dev-server/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "engines": { - "node": ">=4" - } - }, - "node_modules/webpack-dev-server/node_modules/readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dependencies": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/webpack-dev-server/node_modules/resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", - "dependencies": { - "resolve-from": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/webpack-dev-server/node_modules/resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "engines": { - "node": ">=4" - } - }, - "node_modules/webpack-dev-server/node_modules/schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dependencies": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/webpack-dev-server/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/webpack-dev-server/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/string-width/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/string-width/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dependencies": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/ws": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", - "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", - "dependencies": { - "async-limiter": "~1.0.0" - } - }, - "node_modules/webpack-dev-server/node_modules/yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dependencies": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - } - }, - "node_modules/webpack-dev-server/node_modules/yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "node_modules/webpack-log": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", - "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", - "dependencies": { - "ansi-colors": "^3.0.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/webpack-log/node_modules/ansi-colors": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", - "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-log/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/webpack-manifest-plugin": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-2.2.0.tgz", - "integrity": "sha512-9S6YyKKKh/Oz/eryM1RyLVDVmy3NSPV0JXMRhZ18fJsq+AwGxUY34X54VNwkzYcEmEkDwNxuEOboCZEebJXBAQ==", - "dependencies": { - "fs-extra": "^7.0.0", - "lodash": ">=3.5 <5", - "object.entries": "^1.1.0", - "tapable": "^1.0.0" - }, - "engines": { - "node": ">=6.11.5" - }, - "peerDependencies": { - "webpack": "2 || 3 || 4" - } - }, - "node_modules/webpack-manifest-plugin/node_modules/fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/webpack-manifest-plugin/node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/webpack-manifest-plugin/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "dependencies": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - } - }, - "node_modules/webpack-sources/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack/node_modules/acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/webpack/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack/node_modules/cacache": { - "version": "12.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", - "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", - "dependencies": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - } - }, - "node_modules/webpack/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" - }, - "node_modules/webpack/node_modules/eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "dependencies": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/webpack/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/webpack/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "node_modules/webpack/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack/node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "engines": { - "node": ">=4" - } - }, - "node_modules/webpack/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/webpack/node_modules/loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/webpack/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/webpack/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dependencies": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/webpack/node_modules/serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/webpack/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack/node_modules/ssri": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", - "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", - "dependencies": { - "figgy-pudding": "^3.5.1" - } - }, - "node_modules/webpack/node_modules/terser-webpack-plugin": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", - "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", - "dependencies": { - "cacache": "^12.0.2", - "find-cache-dir": "^2.1.0", - "is-wsl": "^1.1.0", - "schema-utils": "^1.0.0", - "serialize-javascript": "^4.0.0", - "source-map": "^0.6.1", - "terser": "^4.1.2", - "webpack-sources": "^1.4.0", - "worker-farm": "^1.7.0" - }, - "engines": { - "node": ">= 6.9.0" - }, - "peerDependencies": { - "webpack": "^4.0.0" - } - }, - "node_modules/webpack/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dependencies": { - "iconv-lite": "0.4.24" - } - }, - "node_modules/whatwg-fetch": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" - }, - "node_modules/whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" - }, - "node_modules/whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "dependencies": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/workbox-background-sync": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-5.1.4.tgz", - "integrity": "sha512-AH6x5pYq4vwQvfRDWH+vfOePfPIYQ00nCEB7dJRU1e0n9+9HMRyvI63FlDvtFT2AvXVRsXvUt7DNMEToyJLpSA==", - "dependencies": { - "workbox-core": "^5.1.4" - } - }, - "node_modules/workbox-broadcast-update": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-5.1.4.tgz", - "integrity": "sha512-HTyTWkqXvHRuqY73XrwvXPud/FN6x3ROzkfFPsRjtw/kGZuZkPzfeH531qdUGfhtwjmtO/ZzXcWErqVzJNdXaA==", - "dependencies": { - "workbox-core": "^5.1.4" - } - }, - "node_modules/workbox-build": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-5.1.4.tgz", - "integrity": "sha512-xUcZn6SYU8usjOlfLb9Y2/f86Gdo+fy1fXgH8tJHjxgpo53VVsqRX0lUDw8/JuyzNmXuo8vXX14pXX2oIm9Bow==", - "dependencies": { - "@babel/core": "^7.8.4", - "@babel/preset-env": "^7.8.4", - "@babel/runtime": "^7.8.4", - "@hapi/joi": "^15.1.0", - "@rollup/plugin-node-resolve": "^7.1.1", - "@rollup/plugin-replace": "^2.3.1", - "@surma/rollup-plugin-off-main-thread": "^1.1.1", - "common-tags": "^1.8.0", - "fast-json-stable-stringify": "^2.1.0", - "fs-extra": "^8.1.0", - "glob": "^7.1.6", - "lodash.template": "^4.5.0", - "pretty-bytes": "^5.3.0", - "rollup": "^1.31.1", - "rollup-plugin-babel": "^4.3.3", - "rollup-plugin-terser": "^5.3.1", - "source-map": "^0.7.3", - "source-map-url": "^0.4.0", - "stringify-object": "^3.3.0", - "strip-comments": "^1.0.2", - "tempy": "^0.3.0", - "upath": "^1.2.0", - "workbox-background-sync": "^5.1.4", - "workbox-broadcast-update": "^5.1.4", - "workbox-cacheable-response": "^5.1.4", - "workbox-core": "^5.1.4", - "workbox-expiration": "^5.1.4", - "workbox-google-analytics": "^5.1.4", - "workbox-navigation-preload": "^5.1.4", - "workbox-precaching": "^5.1.4", - "workbox-range-requests": "^5.1.4", - "workbox-routing": "^5.1.4", - "workbox-strategies": "^5.1.4", - "workbox-streams": "^5.1.4", - "workbox-sw": "^5.1.4", - "workbox-window": "^5.1.4" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/workbox-build/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/workbox-build/node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/workbox-build/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/workbox-build/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/workbox-cacheable-response": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-5.1.4.tgz", - "integrity": "sha512-0bfvMZs0Of1S5cdswfQK0BXt6ulU5kVD4lwer2CeI+03czHprXR3V4Y8lPTooamn7eHP8Iywi5QjyAMjw0qauA==", - "dependencies": { - "workbox-core": "^5.1.4" - } - }, - "node_modules/workbox-core": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-5.1.4.tgz", - "integrity": "sha512-+4iRQan/1D8I81nR2L5vcbaaFskZC2CL17TLbvWVzQ4qiF/ytOGF6XeV54pVxAvKUtkLANhk8TyIUMtiMw2oDg==" - }, - "node_modules/workbox-expiration": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-5.1.4.tgz", - "integrity": "sha512-oDO/5iC65h2Eq7jctAv858W2+CeRW5e0jZBMNRXpzp0ZPvuT6GblUiHnAsC5W5lANs1QS9atVOm4ifrBiYY7AQ==", - "dependencies": { - "workbox-core": "^5.1.4" - } - }, - "node_modules/workbox-google-analytics": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-5.1.4.tgz", - "integrity": "sha512-0IFhKoEVrreHpKgcOoddV+oIaVXBFKXUzJVBI+nb0bxmcwYuZMdteBTp8AEDJacENtc9xbR0wa9RDCnYsCDLjA==", - "dependencies": { - "workbox-background-sync": "^5.1.4", - "workbox-core": "^5.1.4", - "workbox-routing": "^5.1.4", - "workbox-strategies": "^5.1.4" - } - }, - "node_modules/workbox-navigation-preload": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-5.1.4.tgz", - "integrity": "sha512-Wf03osvK0wTflAfKXba//QmWC5BIaIZARU03JIhAEO2wSB2BDROWI8Q/zmianf54kdV7e1eLaIEZhth4K4MyfQ==", - "dependencies": { - "workbox-core": "^5.1.4" - } - }, - "node_modules/workbox-precaching": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-5.1.4.tgz", - "integrity": "sha512-gCIFrBXmVQLFwvAzuGLCmkUYGVhBb7D1k/IL7pUJUO5xacjLcFUaLnnsoVepBGAiKw34HU1y/YuqvTKim9qAZA==", - "dependencies": { - "workbox-core": "^5.1.4" - } - }, - "node_modules/workbox-range-requests": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-5.1.4.tgz", - "integrity": "sha512-1HSujLjgTeoxHrMR2muDW2dKdxqCGMc1KbeyGcmjZZAizJTFwu7CWLDmLv6O1ceWYrhfuLFJO+umYMddk2XMhw==", - "dependencies": { - "workbox-core": "^5.1.4" - } - }, - "node_modules/workbox-routing": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-5.1.4.tgz", - "integrity": "sha512-8ljknRfqE1vEQtnMtzfksL+UXO822jJlHTIR7+BtJuxQ17+WPZfsHqvk1ynR/v0EHik4x2+826Hkwpgh4GKDCw==", - "dependencies": { - "workbox-core": "^5.1.4" - } - }, - "node_modules/workbox-strategies": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-5.1.4.tgz", - "integrity": "sha512-VVS57LpaJTdjW3RgZvPwX0NlhNmscR7OQ9bP+N/34cYMDzXLyA6kqWffP6QKXSkca1OFo/v6v7hW7zrrguo6EA==", - "dependencies": { - "workbox-core": "^5.1.4", - "workbox-routing": "^5.1.4" - } - }, - "node_modules/workbox-streams": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-5.1.4.tgz", - "integrity": "sha512-xU8yuF1hI/XcVhJUAfbQLa1guQUhdLMPQJkdT0kn6HP5CwiPOGiXnSFq80rAG4b1kJUChQQIGPrq439FQUNVrw==", - "dependencies": { - "workbox-core": "^5.1.4", - "workbox-routing": "^5.1.4" - } - }, - "node_modules/workbox-sw": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-5.1.4.tgz", - "integrity": "sha512-9xKnKw95aXwSNc8kk8gki4HU0g0W6KXu+xks7wFuC7h0sembFnTrKtckqZxbSod41TDaGh+gWUA5IRXrL0ECRA==" - }, - "node_modules/workbox-webpack-plugin": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-5.1.4.tgz", - "integrity": "sha512-PZafF4HpugZndqISi3rZ4ZK4A4DxO8rAqt2FwRptgsDx7NF8TVKP86/huHquUsRjMGQllsNdn4FNl8CD/UvKmQ==", - "dependencies": { - "@babel/runtime": "^7.5.5", - "fast-json-stable-stringify": "^2.0.0", - "source-map-url": "^0.4.0", - "upath": "^1.1.2", - "webpack-sources": "^1.3.0", - "workbox-build": "^5.1.4" - }, - "engines": { - "node": ">=8.0.0" - }, - "peerDependencies": { - "webpack": "^4.0.0" - } - }, - "node_modules/workbox-window": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-5.1.4.tgz", - "integrity": "sha512-vXQtgTeMCUq/4pBWMfQX8Ee7N2wVC4Q7XYFqLnfbXJ2hqew/cU1uMTD2KqGEgEpE4/30luxIxgE+LkIa8glBYw==", - "dependencies": { - "workbox-core": "^5.1.4" - } - }, - "node_modules/worker-farm": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", - "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", - "dependencies": { - "errno": "~0.1.7" - } - }, - "node_modules/worker-rpc": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/worker-rpc/-/worker-rpc-0.1.1.tgz", - "integrity": "sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg==", - "dependencies": { - "microevent.ts": "~0.1.1" - } - }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/ws": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", - "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs-parser/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yup": { - "version": "0.32.9", - "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.9.tgz", - "integrity": "sha512-Ci1qN+i2H0XpY7syDQ0k5zKQ/DoxO0LzPg8PAR/X4Mpj6DqaeCoIYEEjDJwhArh3Fa7GWbQQVDZKeXYlSH4JMg==", - "dependencies": { - "@babel/runtime": "^7.10.5", - "@types/lodash": "^4.14.165", - "lodash": "^4.17.20", - "lodash-es": "^4.17.15", - "nanoclone": "^0.2.1", - "property-expr": "^2.0.4", - "toposort": "^2.0.2" - }, - "engines": { - "node": ">=10" - } - } - } -} diff --git a/frontend/package.json b/frontend/package.json index 495a73f..1c3ece1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,51 +1,39 @@ { - "name": "frontend", - "version": "0.1.0", + "name": "@bookit/frontend", "private": true, - "dependencies": { - "@cthit/react-digit-components": "^1.0.4-alpha.2", - "@fullcalendar/core": "^5.9.0", - "@fullcalendar/daygrid": "^5.9.0", - "@fullcalendar/interaction": "^5.9.0", - "@fullcalendar/list": "^5.9.0", - "@fullcalendar/moment": "^5.9.0", - "@fullcalendar/react": "^5.9.0", - "@fullcalendar/timegrid": "^5.9.0", - "@material-ui/icons": "^4.11.2", - "axios": "^0.21.1", - "moment": "^2.29.1", - "prop-types": "^15.7.2", - "react": "^17.0.1", - "react-dom": "^17.0.1", - "react-router-dom": "^5.2.0", - "react-scripts": "4.0.3", - "typescript": "^4.9.5", - "web-vitals": "^1.0.1" - }, + "type": "module", "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "eject": "react-scripts eject" + "dev": "vp dev", + "build": "vp build", + "preview": "vp preview", + "typecheck": "tsc --noEmit" }, - "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest" - ] - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] + "dependencies": { + "@fullcalendar/react": "7.1.0", + "@graphql-typed-document-node/core": "^3.2.0", + "@tanstack/react-query": "^5.102.8", + "@tanstack/react-router": "^1.170.32", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cn": "^0.2.5", + "date-fns": "^4.4.0", + "graphql": "^16.12.0", + "lucide-react": "^1.41.0", + "radix-ui": "^1.6.7", + "react": "^19.2.8", + "react-dom": "^19.2.8", + "sonner": "^2.0.8", + "tailwind-merge": "^3.6.0", + "temporal-polyfill": "^1.0.4" }, - "resolutions": { - "react-error-overlay": "6.0.9" + "devDependencies": { + "@tailwindcss/vite": "^4.3.3", + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.7", + "@vitejs/plugin-react": "^6.1.1", + "tailwindcss": "^4.3.3", + "tw-animate-css": "^1.4.0", + "typescript": "^7.0.2", + "vite-plus": "0.3.0" } } diff --git a/frontend/public/index.html b/frontend/public/index.html deleted file mode 100644 index bfec3b8..0000000 --- a/frontend/public/index.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - BookIT - - - - -
- - diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json index 8e1e218..812f0d5 100644 --- a/frontend/public/manifest.json +++ b/frontend/public/manifest.json @@ -12,5 +12,4 @@ "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff" - -} \ No newline at end of file +} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx deleted file mode 100644 index d37abb9..0000000 --- a/frontend/src/App.jsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Route, Switch } from "react-router-dom"; -import { DigitProviders } from "@cthit/react-digit-components"; -import { UserProvider } from "./common/contexts/user-context"; - -import Header from "./common/components/header"; -import Home from "./use-cases/home"; -import NewReservation from "./use-cases/new-event"; -import Rules from "./use-cases/rules"; -import EditEvent from "./use-cases/edit-event"; - -const App = () => ( - - -
- - - - - -

Page not found

} /> -
-
-
-
-); - -export default App; diff --git a/frontend/src/api/backend.api.js b/frontend/src/api/backend.api.js deleted file mode 100644 index 08ecbfa..0000000 --- a/frontend/src/api/backend.api.js +++ /dev/null @@ -1,178 +0,0 @@ -import Axios from "axios"; -import { - getEvents_query, - createEvent_query, - getRules_query, - getRule_query, - createRule_query, - deleteRule_query, - getUser_query, - getEvent_query, - getFullEvent_query, - editEvent_query, - deleteEvent_query, - getIllegalSlots_query, -} from "./backend.queries"; - -const graphql_endpoint = "/api/graphql/v1"; - -const request = (body, dataLabel, errorMessage, onReject = () => null) => - new Promise(resolve => - Axios.post(graphql_endpoint, body) - .then(res => { - if (res.data.errors) { - console.log(errorMessage); - console.log(res.data.errors); - resolve(onReject(res)); - return; - } - resolve(res.data.data[dataLabel]); - }) - .catch(err => { - console.log(errorMessage); - console.log(err); - resolve(onReject(err)); - }), - ); - -export const getEvents = (from, to) => - request( - { - query: getEvents_query, - variables: { from: from, to: to }, - operationName: "GetEvents", - }, - "eventsFT", - "Failed to fetch events", - () => [], - ); - -export const getEvent = id => - request( - { - query: getEvent_query, - variables: { id: id }, - operationName: "GetEvent", - }, - "event", - "Failed to fetch event: " + id, - () => {}, - ); - -export const getFullEvent = id => - request( - { - query: getFullEvent_query, - variables: { id: id }, - operationName: "GetFullEvent", - }, - "event", - "Failed to fetch event: " + id, - () => {}, - ); - -export const createEvent = event => - request( - { - query: createEvent_query, - variables: { event: event }, - operationName: "CreateEvent", - }, - "createEvent", - "Failed to create event", - err => err.message, - ); - -export const editEvent = event => - request( - { - query: editEvent_query, - variables: { event: event }, - operationName: "EditEvent", - }, - "editEvent", - "Failed to edit event", - () => {}, - ); - -export const deleteEvent = id => - request( - { - query: deleteEvent_query, - variables: { id: id }, - operationName: "DeleteEvent", - }, - "deleteEvent", - "Failed to delete event", - () => {}, - ); - -export const getRules = () => - request( - { - query: getRules_query, - }, - "rules", - "Failed to fetch rules", - () => [], - ); - -export const getRule = id => - request( - { - query: getRule_query, - variables: { id: id }, - operationName: "GetRule", - }, - "rule", - "Failed to fetch rule", - () => ({}), - ); - -export const createRule = rule => - request( - { - query: createRule_query, - operationName: "CreateRule", - variables: { rule: rule }, - }, - "createRule", - "Failed to create rule", - err => err.message, - ); - -export const deleteRule = id => - request( - { - query: deleteRule_query, - variables: { id: id }, - operationName: "DeleteRule", - }, - "deleteRule", - "Failed to delete rule", - err => err.message, - ); - -export const getIllegalSlots = (from, to) => - request( - { - query: getIllegalSlots_query, - variables: { from, to }, - operationName: "GetIllegalSlots", - }, - "illegalSlots", - "Failed to fetch illegal slots", - () => [], - ); - -export const getUser = () => - new Promise((resolve, reject) => - Axios.post(graphql_endpoint, { - query: getUser_query, - }) - .then(res => resolve(res.data.data.user)) - .catch(err => { - reject(err); - }), - ); - diff --git a/frontend/src/api/backend.queries.js b/frontend/src/api/backend.queries.js deleted file mode 100644 index 9bc15f0..0000000 --- a/frontend/src/api/backend.queries.js +++ /dev/null @@ -1,150 +0,0 @@ -export const getEvents_query = ` -query GetEvents($from: String!, $to: String!){ - eventsFT(from: $from, to: $to) { - start - end - id - title - room - booked_as - phone - } -}`; - -export const getEvent_query = ` -query GetEvent($id: String!) { - event(id: $id) { - title - description - start - end - booked_as - booked_by - created_at - room - } -} -`; - -export const getFullEvent_query = ` -query GetFullEvent($id: String) { - event(id: $id) { - start - end - description - title - room - phone - booked_as - } -} -`; - -export const createEvent_query = ` -mutation CreateEvent($event: InputEvent!) { - createEvent(event: $event) { - sv - en - } -}`; - -export const getRules_query = ` -{ - rules { - id - title - start_date - end_date - start_time - end_time - day_mask - priority - room - allow - } -} -`; - -export const getRule_query = ` -query GetRule($id: String!) { - rule(id: $id) { - id - title - start_date - end_date - start_time - end_time - day_mask - priority - description - room - allow - created_at - updated_at - } -} -`; - -export const createRule_query = ` -mutation CreateRule($rule: InputRule!) { - createRule(rule: $rule) { - sv - en - } -} -`; - -export const deleteRule_query = ` -mutation DeleteRule($id: String!) { - deleteRule(id: $id) -} -`; - -export const getIllegalSlots_query = ` -query GetIllegalSlots($from: String!, $to: String!) { - illegalSlots(from: $from, to: $to) { - start - end - title - description - } -} -`; - -export const getUser_query = ` -{ - user { - cid - groups - is_admin - locale - } -} -`; - -export const editEvent_query = ` -mutation EditEvent($event: InputEvent) { - editEvent(event: $event) { - sv - en - } -} -`; - -export const deleteEvent_query = ` -mutation DeleteEvent($id: String) { - deleteEvent(id: $id) { - sv - en - } -} -`; - -export const set_report_status_query = ` -mutation SetStatus($status: InputStatus!) { - set_report_status(status: $status) { - sv - en - } -} -`; diff --git a/frontend/src/api/client.test.ts b/frontend/src/api/client.test.ts new file mode 100644 index 0000000..fc709d5 --- /dev/null +++ b/frontend/src/api/client.test.ts @@ -0,0 +1,32 @@ +import { afterEach, describe, expect, it, vi } from "vite-plus/test"; +import { CurrentUserDocument } from "../generated/graphql"; +import { checkMutation, request } from "./client"; + +afterEach(() => vi.unstubAllGlobals()); + +describe("GraphQL transport", () => { + it("sends credentials and rejects GraphQL failures even on HTTP 200", async () => { + const fetch = vi.fn().mockResolvedValue(Response.json({ errors: [{ message: "Denied" }] })); + vi.stubGlobal("fetch", fetch); + await expect(request(CurrentUserDocument, {})).rejects.toThrow("Denied"); + expect(fetch).toHaveBeenCalledWith( + "/api/graphql/v1", + expect.objectContaining({ + credentials: "same-origin", + method: "POST", + }), + ); + }); + it("reports expired authentication", async () => { + vi.stubGlobal("fetch", vi.fn().mockResolvedValue(new Response(null, { status: 401 }))); + await expect(request(CurrentUserDocument, {})).rejects.toThrow("session expired"); + }); + it("rejects an empty response rather than rendering false success", async () => { + vi.stubGlobal("fetch", vi.fn().mockResolvedValue(Response.json({}))); + await expect(request(CurrentUserDocument, {})).rejects.toThrow("no data"); + }); + it("preserves localized mutation validation errors", () => { + expect(() => checkMutation({ en: "Denied", sv: "Nekad" }, "sv")).toThrow("Nekad"); + expect(() => checkMutation(null, "en")).not.toThrow(); + }); +}); diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts new file mode 100644 index 0000000..491303a --- /dev/null +++ b/frontend/src/api/client.ts @@ -0,0 +1,29 @@ +import { print } from "graphql"; +import type { TypedDocumentNode } from "@graphql-typed-document-node/core"; + +export async function request>( + document: TypedDocumentNode, + variables: V, +): Promise { + const response = await fetch("/api/graphql/v1", { + method: "POST", + credentials: "same-origin", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ query: print(document), variables }), + }); + if (response.status === 401 || response.redirected) + throw new Error("Your session expired. Please sign in again."); + if (!response.ok) throw new Error(`Request failed (${response.status}). Please try again.`); + const result = (await response.json()) as { data?: T; errors?: { message: string }[] }; + if (result.errors?.length) + throw new Error(result.errors.map((error) => error.message).join(". ")); + if (!result.data) throw new Error("The server returned no data."); + return result.data; +} + +export function checkMutation( + error: { en: string; sv: string } | null | undefined, + language: "en" | "sv", +) { + if (error) throw new Error(error[language]); +} diff --git a/frontend/src/api/operations.graphql b/frontend/src/api/operations.graphql new file mode 100644 index 0000000..a1e78f8 --- /dev/null +++ b/frontend/src/api/operations.graphql @@ -0,0 +1,88 @@ +fragment Booking on Event { + id + title + description + start + end + room + phone + booked_as + booked_by + created_at + updated_at +} +query CurrentUser { + user { + sub + cid + name + nickname + locale + groups + is_admin + } +} +query Calendar($from: String!, $to: String!) { + eventsFT(from: $from, to: $to) { + ...Booking + } + illegalSlots(from: $from, to: $to) { + start + end + title + description + room + } +} +query BookingDetail($id: String!) { + event(id: $id) { + ...Booking + } +} +mutation CreateBooking($event: InputEvent!) { + createEvent(event: $event) { + en + sv + } +} +mutation UpdateBooking($event: InputEvent!) { + editEvent(event: $event) { + en + sv + } +} +mutation DeleteBooking($id: String!) { + deleteEvent(id: $id) { + en + sv + } +} +query Rules { + rules { + id + created_at + updated_at + title + description + day_mask + start_date + end_date + start_time + end_time + priority + allow + room + } +} +mutation CreateRule($rule: InputRule!) { + createRule(rule: $rule) { + en + sv + } +} +mutation DeleteRule($id: String!) { + deleteRule(id: $id) { + en + sv + } +} diff --git a/frontend/src/common/components/event-form/elements/book-as.element.jsx b/frontend/src/common/components/event-form/elements/book-as.element.jsx deleted file mode 100644 index 4bab962..0000000 --- a/frontend/src/common/components/event-form/elements/book-as.element.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import { - DigitAutocompleteSelectSingle, - useDigitFormField, -} from "@cthit/react-digit-components"; - -const BookAs = ({ groups, label }) => { - const bookedAsValues = useDigitFormField("booked_as"); - return ( - ({ text: e, value: e }))} - /> - ); -}; - -export default BookAs; diff --git a/frontend/src/common/components/event-form/elements/booking-terms.element.jsx b/frontend/src/common/components/event-form/elements/booking-terms.element.jsx deleted file mode 100644 index 249a6f0..0000000 --- a/frontend/src/common/components/event-form/elements/booking-terms.element.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import { - DigitCheckbox, - useDigitFormField, -} from "@cthit/react-digit-components"; - -const BookingTerms = ({ preLinkLabel, linkLabel }) => { - const bookingTermsValues = useDigitFormField("booking_terms"); - - return ( - - {preLinkLabel} - - {linkLabel} - -

- } - size={{ width: "100%" }} - /> - ); -}; - -export default BookingTerms; diff --git a/frontend/src/common/components/event-form/elements/cubsec.element.jsx b/frontend/src/common/components/event-form/elements/cubsec.element.jsx deleted file mode 100644 index c41b757..0000000 --- a/frontend/src/common/components/event-form/elements/cubsec.element.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import { - DigitCheckbox, - useDigitFormField, - } from "@cthit/react-digit-components"; - - const Cubsec = ({ preLinkLabel, linkLabel }) => { - const cubsecValues = useDigitFormField("cubsec"); - - return ( - - {preLinkLabel} - - {linkLabel} - -

- } - size={{ width: "100%" }} - /> - ); - }; - - export default Cubsec; diff --git a/frontend/src/common/components/event-form/elements/date-time-picker.element.jsx b/frontend/src/common/components/event-form/elements/date-time-picker.element.jsx deleted file mode 100644 index aed3cf5..0000000 --- a/frontend/src/common/components/event-form/elements/date-time-picker.element.jsx +++ /dev/null @@ -1,23 +0,0 @@ -import { - useDigitFormField, - DigitDateAndTimePicker, -} from "@cthit/react-digit-components"; -import { string } from "prop-types"; -import { useEffect } from "react"; -import moment from "moment"; -import "moment/locale/sv"; - -const TimeAndTimePicker = ({ name, label }) => { - const timeValues = useDigitFormField(name); - useEffect(() => { - moment.locale("sv", { week: { dow: 0 } }); - }, []); - return ; -}; - -TimeAndTimePicker.propTypes = { - name: string.isRequired, - label: string, -}; - -export default TimeAndTimePicker; diff --git a/frontend/src/common/components/event-form/elements/description.element.jsx b/frontend/src/common/components/event-form/elements/description.element.jsx deleted file mode 100644 index 177428d..0000000 --- a/frontend/src/common/components/event-form/elements/description.element.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import { - useDigitFormField, - DigitTextArea, -} from "@cthit/react-digit-components"; - -const Description = ({ label }) => { - const descriptionValues = useDigitFormField("description"); - return ( - - ); -}; - -export default Description; diff --git a/frontend/src/common/components/event-form/elements/gdpr.element.jsx b/frontend/src/common/components/event-form/elements/gdpr.element.jsx deleted file mode 100644 index ac11bd4..0000000 --- a/frontend/src/common/components/event-form/elements/gdpr.element.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import { - DigitCheckbox, - useDigitFormField, -} from "@cthit/react-digit-components"; - -const GDPR = ({ preLinkLabel, linkLabel, onLinkClick }) => { - const gdprValues = useDigitFormField("gdpr"); - - return ( - - {preLinkLabel} {/*eslint-disable-next-line*/} - onLinkClick()}> - {linkLabel} - -

- } - size={{ width: "100%" }} - /> - ); -}; - -export default GDPR; diff --git a/frontend/src/common/components/event-form/elements/index.jsx b/frontend/src/common/components/event-form/elements/index.jsx deleted file mode 100644 index edd3210..0000000 --- a/frontend/src/common/components/event-form/elements/index.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import Title from "./title.element"; -import PhoneNumber from "./phone-number.element"; -import TimeAndTimePicker from "./date-time-picker.element"; -import Description from "./description.element"; -import Rooms from "./rooms.element"; -import BookAs from "./book-as.element"; -import GDPR from "./gdpr.element"; -import Cubsec from "./cubsec.element"; - -export { - Title, - PhoneNumber, - TimeAndTimePicker, - Description, - Rooms, - BookAs, - GDPR, - Cubsec, -}; diff --git a/frontend/src/common/components/event-form/elements/phone-number.element.jsx b/frontend/src/common/components/event-form/elements/phone-number.element.jsx deleted file mode 100644 index 4eae0d4..0000000 --- a/frontend/src/common/components/event-form/elements/phone-number.element.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import { - useDigitFormField, - DigitTextField, -} from "@cthit/react-digit-components"; -import { string } from "prop-types"; - -const PhoneNumber = ({ name, label, ...props }) => { - const phoneValues = useDigitFormField(name); - return ; -}; - -PhoneNumber.propTypes = { - name: string.isRequired, - label: string, -}; - -export default PhoneNumber; diff --git a/frontend/src/common/components/event-form/elements/rooms.element.jsx b/frontend/src/common/components/event-form/elements/rooms.element.jsx deleted file mode 100644 index 5805e6d..0000000 --- a/frontend/src/common/components/event-form/elements/rooms.element.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import { - DigitAutocompleteSelectMultiple, - useDigitFormField, -} from "@cthit/react-digit-components"; - -const Rooms = ({ rooms, label }) => { - const roomValues = useDigitFormField("room"); - return ( - - ); -}; - -export default Rooms; diff --git a/frontend/src/common/components/event-form/elements/title.element.jsx b/frontend/src/common/components/event-form/elements/title.element.jsx deleted file mode 100644 index 8d34b0e..0000000 --- a/frontend/src/common/components/event-form/elements/title.element.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import { - DigitTextField, - useDigitFormField, -} from "@cthit/react-digit-components"; - -const Title = ({ label }) => { - const titleValues = useDigitFormField("title"); - return ( - - ); -}; - -export default Title; diff --git a/frontend/src/common/components/event-form/event.form.css b/frontend/src/common/components/event-form/event.form.css deleted file mode 100644 index 03cc31c..0000000 --- a/frontend/src/common/components/event-form/event.form.css +++ /dev/null @@ -1,4 +0,0 @@ -.gdpr-text { - max-width: 100rem; - max-height: 100rem; -} diff --git a/frontend/src/common/components/event-form/event.form.jsx b/frontend/src/common/components/event-form/event.form.jsx deleted file mode 100644 index 8e8c3eb..0000000 --- a/frontend/src/common/components/event-form/event.form.jsx +++ /dev/null @@ -1,160 +0,0 @@ -import { useContext, useEffect, useState } from "react"; -import { - DigitForm, - DigitLayout, - DigitLoading, - DigitButton, - useDigitToast, - useDigitTranslations, - useDigitCustomDialog, - DigitText, -} from "@cthit/react-digit-components"; -import * as yup from "yup"; -import { - Title, - TimeAndTimePicker, - Description, - Rooms, - PhoneNumber, - BookAs, - GDPR, - Cubsec -} from "./elements"; -import UserContext from "../../contexts/user-context"; -import ROOMS from "../../rooms"; -import translations from "./event.form.translations.json"; -import propTypes from "prop-types"; -import BookingTerms from "./elements/booking-terms.element"; -import GDPRAgreement from "./gdpr-agreement"; -import "./event.form.css"; - -const regexStrings = { - // eslint-disable-next-line - phone: /^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,5}$/im, - // eslint-disable-next-line - email: /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/, -}; - -const EventFrom = ({ onSubmit, initialValues }) => { - const [openToast] = useDigitToast({ - duration: 3000, - actionText: "Ok", - actionHandler: () => {}, - }); - const [user] = useContext(UserContext); - const [texts, activeLanguage] = useDigitTranslations(translations); - const [loading, setLoading] = useState(true); - const [openDialog] = useDigitCustomDialog({ - title: texts.gdpr_agreement, - }); - - useEffect(() => { - setLoading(initialValues === null); - }, [initialValues]); - - const validationSchema = yup.object().shape({ - title: yup.string().required(texts.title_required), - phone: yup - .string() - .matches(regexStrings.phone, texts.phone_invalid) - .required(texts.phone_required), - room: yup.array().min(1, texts.room_required), - description: yup.string(), - start: yup.date().required(), - end: yup.date().required(), - booking_terms: yup.bool().isTrue().required(texts.booking_terms_required), - gdpr: yup.bool().isTrue().required(texts.gdpr_required), - cubsec: yup.bool().isTrue().required(texts.cubsec_required), - }); - - return ( - <> - {loading ? ( - - ) : ( - { - validationSchema - .validate(values) - .then(() => onSubmit(values)) - .catch(err => - openToast({ - text: err.message, - }), - ); - }} - render={() => ( - - {/**/} - - <PhoneNumber - name="phone" - label={texts.phone} - size={{ width: "100%" }} - /> - <Rooms label={texts.room} rooms={ROOMS} /> - <DigitLayout.Row flexWrap="wrap" display="flex"> - <TimeAndTimePicker name="start" label={texts.start} /> - <TimeAndTimePicker name="end" label={texts.end} /> - </DigitLayout.Row> - <Description label={texts.description} /> - <BookAs label={texts.booked_as} groups={user.groups} /> - - <BookingTerms - preLinkLabel={texts.i_accept} - linkLabel={texts.booking_terms} - /> - - <GDPR - preLinkLabel={texts.i_accept} - linkLabel={texts.gdpr_agreement} - onLinkClick={() => - openDialog({ - renderMain: () => ( - <div className="gdpr-text"> - {GDPRAgreement[activeLanguage].split("\n").map(t => ( - <div> - <DigitText.Text text={t} /> - <br /> - </div> - ))} - </div> - ), - }) - } - /> - - <Cubsec - preLinkLabel={texts.cubsec_condition} - linkLabel={texts.cubsec_notified} - /> - - <DigitButton - raised - submit - size={{ maxWidth: "100%" }} - text={texts.submit} - /> - </DigitLayout.Column> - )} - /> - )} - </> - ); -}; - -EventFrom.propTypes = { - initialValues: propTypes.shape({ - title: propTypes.string, - phone: propTypes.string, - room: propTypes.arrayOf(propTypes.string), - start: propTypes.objectOf(Date), - end: propTypes.objectOf(Date), - description: propTypes.string, - booked_as: propTypes.string, - }), -}; - -export default EventFrom; diff --git a/frontend/src/common/components/event-form/event.form.translations.json b/frontend/src/common/components/event-form/event.form.translations.json deleted file mode 100644 index 3ab5205..0000000 --- a/frontend/src/common/components/event-form/event.form.translations.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "new_booking": ["New Booking", "Ny bokning"], - "title": ["Title", "Titel"], - "phone": ["Phone number", "Telefonnummer"], - "room": ["Room", "Rum"], - "start": ["Begins at", "Börjar"], - "end": ["Ends at", "Slutar"], - "description": ["Description", "Beskrivning"], - "booked_as": ["Booking as", "Bokas som"], - "new_event_created": ["A new event was created", "Ett nytt event skapades"], - "booking_terms": ["booking terms and conditions", "bokningsvilkoren"], - "i_accept": ["I accept the ", "Jag godkänner "], - "gdpr_required": [ - "You need to accept the GDPR agreement", - "Du måste godkänna GDPR-avtalet" - ], - "gdpr_agreement": ["GDPR agreement", "GDPR-avtalet"], - "title_required": [ - "You need to provide a title for the event", - "Du måste ange en titel för bokningen" - ], - "name_required": ["You need to provide a name", "Du måste ange ett namn"], - "phone_required": [ - "You need to provide a phone number", - "Du måste ange ett telefonnummer" - ], - "phone_invalid": [ - "Illegal characters or faulty formatting of phone number", - "Ogiltigt format på telefonnummer" - ], - "room_required": [ - "You need to select at least one room", - "Du måste välja minst ett rum" - ], - "booking_terms_required": [ - "You need to accept the booking terms and conditions", - "Du måste godkänna bokningsvillkoren" - ], - "submit": ["Submit", "Skicka"], - "cubsec_condition": [ - "If this is related to an arrangement, I have ", - "Om det gäller ett arrangemang har jag " - ], - "cubsec_notified": [ - "notified Cubsec/Chalmers", - "anmält det till Cubsec/Chalmers" - ], - "cubsec_required": [ - "You must confirm that you have notified Cubsec/Chalmers if applicable", - "Du måste bekräfta en eventuell anmälan till Cubsec/Chalmers" - ] -} diff --git a/frontend/src/common/components/event-form/index.jsx b/frontend/src/common/components/event-form/index.jsx deleted file mode 100644 index faeee18..0000000 --- a/frontend/src/common/components/event-form/index.jsx +++ /dev/null @@ -1,3 +0,0 @@ -import EventForm from "./event.form"; - -export default EventForm; diff --git a/frontend/src/common/components/header/index.jsx b/frontend/src/common/components/header/index.jsx deleted file mode 100644 index fb7643b..0000000 --- a/frontend/src/common/components/header/index.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import Tabs from "./tabs.component"; -import { DigitHeader, DigitText } from "@cthit/react-digit-components"; -import { Link } from "react-router-dom"; - -const Header = ({ children }) => { - return ( - <DigitHeader - title="" - mainPadding="0" - renderHeader={() => ( - <div style={{ width: "100%", flexDirection: "row", display: "flex", placeItems: "center"}}> - <Link to="/" style={{ textDecoration: "none", color: "white" }}> - <DigitText.Heading5 text="BookIT" /> - </Link> - <div style={{marginLeft: "2em"}}> - <Tabs/> - </div> - </div> - )} - renderMain={() => <>{children}</>} - /> - ); -}; - -export default Header; diff --git a/frontend/src/common/components/header/tabs.component.jsx b/frontend/src/common/components/header/tabs.component.jsx deleted file mode 100644 index 254d687..0000000 --- a/frontend/src/common/components/header/tabs.component.jsx +++ /dev/null @@ -1,45 +0,0 @@ -import { DigitTabs } from "@cthit/react-digit-components"; -import { useEffect, useState } from "react"; -import { useHistory } from "react-router-dom"; -import { useDigitTranslations } from "@cthit/react-digit-components"; -import translations from "./tabs.translations.json"; - -const tabs = [ - { - text: "Calendar", - value: "", - }, - { - text: "Rules", - value: "rules", - }, -]; - -const Tabs = () => { - const [activeTab, setActiveTab] = useState(""); - const history = useHistory(); - const [texts] = useDigitTranslations(translations); - - useEffect(() => { - const value = window.location.pathname.split("/")[1]; - if (!value) return; - const tab = tabs.find(e => e.value === value); - if (!tab) return; - setActiveTab(value); - }, []); - - return ( - <DigitTabs - onChange={value => { - setActiveTab(value); - history.push("/" + value); - }} - centered - selected={activeTab} - fullwidth - tabs={tabs.map(t => ({ ...t, text: texts[t.text] }))} - /> - ); -}; - -export default Tabs; diff --git a/frontend/src/common/components/header/tabs.translations.json b/frontend/src/common/components/header/tabs.translations.json deleted file mode 100644 index c5f67d9..0000000 --- a/frontend/src/common/components/header/tabs.translations.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "Calendar": ["Calendar", "Kalender"], - "Rules": ["Rules", "Regler"] -} diff --git a/frontend/src/common/contexts/user-context/index.jsx b/frontend/src/common/contexts/user-context/index.jsx deleted file mode 100644 index 2b83038..0000000 --- a/frontend/src/common/contexts/user-context/index.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { getUser } from "../../../api/backend.api"; -import { useDigitTranslations } from "@cthit/react-digit-components"; - -export const user_default = { - cid: "", - groups: [], - is_admin: false, - is_logged_in: false, - locale: "en", -}; - -const UserContext = React.createContext([user_default, () => {}]); - -export const UserProvider = ({ children }) => { - const [user, setUser] = useState(user_default); - const [, , setActiveLanguage] = useDigitTranslations({}); - useEffect(() => { - getUser() - .then(res => { - setActiveLanguage(res.locale); - setUser(res); - }) - .catch(error => { - console.log(error) - }); - }, [setActiveLanguage]); - return ( - <UserContext.Provider value={[user, setUser]}> - {children} - </UserContext.Provider> - ); -}; - -export default UserContext; diff --git a/frontend/src/common/elements/add-event-button/add-event-button.css b/frontend/src/common/elements/add-event-button/add-event-button.css deleted file mode 100644 index c2ca1b8..0000000 --- a/frontend/src/common/elements/add-event-button/add-event-button.css +++ /dev/null @@ -1,9 +0,0 @@ -.add-event-button { - z-index: 1; - position: fixed; - bottom: 2rem; - right: 2rem; - border-radius: 5rem; - background-color: #2196f3; - color: #fff; -} diff --git a/frontend/src/common/elements/add-event-button/index.jsx b/frontend/src/common/elements/add-event-button/index.jsx deleted file mode 100644 index cf862b0..0000000 --- a/frontend/src/common/elements/add-event-button/index.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import { DigitIconButton } from "@cthit/react-digit-components"; -import AddIcon from "@material-ui/icons/Add"; -import { useHistory } from "react-router-dom"; -import "./add-event-button.css"; - -const AddEventButton = () => { - const history = useHistory(); - - return ( - <div className="add-event-button"> - <DigitIconButton - icon={AddIcon} - onClick={() => history.push("/new-event")} - /> - </div> - ); -}; - -export default AddEventButton; diff --git a/frontend/src/common/hooks/change-calendar-size.js b/frontend/src/common/hooks/change-calendar-size.js deleted file mode 100644 index 8da1d63..0000000 --- a/frontend/src/common/hooks/change-calendar-size.js +++ /dev/null @@ -1,28 +0,0 @@ -import { useEffect, useState } from "react"; -// Hook -export default function useHeight() { - const [windowHeight, setWindowHeight] = useState({ - height: undefined, - }); - - useEffect(() => { - // Handler to call on window resize - function handleResize() { - // Set window width/height to state - setWindowHeight({ - height: window.innerHeight, - }); - } - - // Add event listener - window.addEventListener("resize", handleResize); - - // Call handler right away so state gets updated with initial window size - handleResize(); - - // Remove event listener on cleanup - return () => window.removeEventListener("resize", handleResize); - }, []); // Empty array ensures that effect is only run on mount - - return windowHeight; - } \ No newline at end of file diff --git a/frontend/src/common/hooks/use-mobile-query.js b/frontend/src/common/hooks/use-mobile-query.js deleted file mode 100644 index bea53fa..0000000 --- a/frontend/src/common/hooks/use-mobile-query.js +++ /dev/null @@ -1,20 +0,0 @@ -import { useEffect, useState } from "react"; - -export default function useMobileQuery() { - const [isMobile, setIsMobile] = useState(false); - - useEffect(() => { - const mediaQuery = window.matchMedia("(max-width: 680px)"); - const handleChange = e => { - setIsMobile(e.matches); - }; - mediaQuery.addEventListener("change", handleChange); - handleChange(mediaQuery); - - return () => { - mediaQuery.removeEventListener("change", handleChange); - }; - }); - - return isMobile; -} diff --git a/frontend/src/common/rooms.js b/frontend/src/common/rooms.js deleted file mode 100644 index b785876..0000000 --- a/frontend/src/common/rooms.js +++ /dev/null @@ -1,24 +0,0 @@ -const ROOMS = [ - { - value: "BIG_HUB", - text: "Storhubben", - color: "#0C6291", - }, - { - value: "GROUP_ROOM", - text: "Grupprummet", - color: "#E28413", - }, - { - value: "CTC", - text: "CTC", - color: "#A63446", - }, - { - value: "THE_CLOUD", - text: "The Cloud", - color: "#3F784C", - }, -]; - -export default ROOMS; diff --git a/frontend/src/components/booking-form.tsx b/frontend/src/components/booking-form.tsx new file mode 100644 index 0000000..314ffab --- /dev/null +++ b/frontend/src/components/booking-form.tsx @@ -0,0 +1,303 @@ +import { formText } from "@/lib/forms"; +import { useState, type FormEvent } from "react"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { useNavigate } from "@tanstack/react-router"; +import { addHours } from "date-fns"; +import { toast } from "sonner"; +import { request, checkMutation } from "@/api/client"; +import { + CreateBookingDocument, + UpdateBookingDocument, + type BookingFragment, + type Room, +} from "@/generated/graphql"; +import { rooms } from "@/lib/rooms"; +import { localInput, parseDate } from "@/lib/dates"; +import { useUser } from "@/lib/user"; +import { useLanguage } from "@/lib/language"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { Checkbox } from "@/components/ui/checkbox"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, + DialogDescription, +} from "@/components/ui/dialog"; +import { privacyText } from "@/lib/privacy"; + +export function BookingForm({ + booking, + initialStart, + initialEnd, +}: { + booking?: BookingFragment; + initialStart?: string; + initialEnd?: string; +}) { + const { data: user } = useUser(); + const { t, language } = useLanguage(); + const navigate = useNavigate(); + const queryClient = useQueryClient(); + const [selected, setSelected] = useState<Room[]>( + booking?.room.filter((room): room is Room => Boolean(room)) ?? ["BIG_HUB"], + ); + const [terms, setTerms] = useState(Boolean(booking)); + const [privacy, setPrivacy] = useState(Boolean(booking)); + const [notification, setNotification] = useState(Boolean(booking)); + const [error, setError] = useState(""); + const [defaultStart] = useState(() => new Date()); + const groups = user?.groups?.filter((group): group is string => Boolean(group)) ?? []; + const eligibleGroups = + user?.is_admin && booking && !groups.includes(booking.booked_as) + ? [...groups, booking.booked_as] + : groups; + const start = booking + ? localInput(parseDate(booking.start)) + : initialStart || localInput(defaultStart); + const end = booking + ? localInput(parseDate(booking.end)) + : initialEnd || localInput(addHours(defaultStart, 1)); + const mutation = useMutation({ + mutationFn: async (form: FormData) => { + const from = new Date(formText(form, "start")); + const to = new Date(formText(form, "end")); + if (!(to > from)) + throw new Error( + t("End time must be after start time.", "Sluttiden måste vara efter starttiden."), + ); + if (!selected.length) throw new Error(t("Select at least one room.", "Välj minst ett rum.")); + if (!terms || !privacy || !notification) + throw new Error( + t("Please confirm all three booking conditions.", "Bekräfta alla tre bokningsvillkor."), + ); + const event = { + ...(booking?.id ? { id: booking.id } : {}), + title: formText(form, "title").trim(), + phone: formText(form, "phone").trim() || null, + start: from.toISOString(), + end: to.toISOString(), + room: selected, + description: formText(form, "description"), + booked_as: formText(form, "booked_as"), + booking_terms: terms, + }; + if (booking) { + const data = await request(UpdateBookingDocument, { event }); + checkMutation(data.editEvent, language); + } else { + const data = await request(CreateBookingDocument, { event }); + checkMutation(data.createEvent, language); + } + }, + onSuccess: async () => { + await queryClient.invalidateQueries({ queryKey: ["calendar"] }); + await queryClient.invalidateQueries({ queryKey: ["booking"] }); + toast.success(t("Booking saved", "Bokningen sparades")); + await navigate({ to: "/" }); + }, + onError: (reason: Error) => setError(reason.message), + }); + function submit(event: FormEvent<HTMLFormElement>) { + event.preventDefault(); + setError(""); + mutation.mutate(new FormData(event.currentTarget)); + } + if (!eligibleGroups.length) + return ( + <p role="alert" className="rounded-lg bg-amber-50 p-5"> + {t( + "You need membership in an active group to make a booking.", + "Du behöver vara medlem i en aktiv grupp för att boka.", + )} + </p> + ); + return ( + <form onSubmit={submit} className="space-y-7"> + <div className="grid gap-5 sm:grid-cols-2"> + <div className="field sm:col-span-2"> + <Label htmlFor="title">{t("Title", "Titel")}</Label> + <Input + id="title" + name="title" + required + maxLength={200} + defaultValue={booking?.title} + placeholder={t("What are you planning?", "Vad planerar ni?")} + /> + </div> + <div className="field"> + <Label htmlFor="phone">{t("Phone number", "Telefonnummer")}</Label> + <Input + id="phone" + name="phone" + type="tel" + autoComplete="tel" + required={!booking} + pattern={String.raw`\+?\(?[0-9]{3}\)?[\-\s.]?[0-9]{3}[\-\s.]?[0-9]{4,5}`} + defaultValue={booking?.phone ?? ""} + placeholder={ + booking + ? t("Leave blank to keep existing number", "Lämna tomt för att behålla numret") + : "070 123 45 67" + } + /> + </div> + <div className="field"> + <Label htmlFor="booked_as">{t("Booking as", "Bokas som")}</Label> + <select + id="booked_as" + name="booked_as" + className="native-select" + defaultValue={booking?.booked_as ?? ""} + required + > + <option value="" disabled> + {t("Select group", "Välj grupp")} + </option> + {eligibleGroups.map((group) => ( + <option key={group} value={group}> + {group} + </option> + ))} + </select> + </div> + </div> + <fieldset> + <legend className="mb-3 text-sm font-medium">{t("Rooms", "Rum")}</legend> + <div className="grid gap-3 sm:grid-cols-2"> + {rooms.map((room) => ( + <label + key={room.id} + className={`flex cursor-pointer items-center gap-3 rounded-lg border p-4 ${selected.includes(room.id) ? "border-primary bg-blue-50/50" : ""}`} + > + <Checkbox + aria-label={room.name} + checked={selected.includes(room.id)} + onCheckedChange={(checked) => + setSelected( + checked ? [...selected, room.id] : selected.filter((id) => id !== room.id), + ) + } + /> + <span className="size-2 rounded-full" style={{ background: room.color }} /> + <span className="text-sm">{room.name}</span> + </label> + ))} + </div> + </fieldset> + <div className="grid gap-5 sm:grid-cols-2"> + <div className="field"> + <Label htmlFor="start">{t("Begins at", "Börjar")}</Label> + <Input id="start" name="start" type="datetime-local" required defaultValue={start} /> + </div> + <div className="field"> + <Label htmlFor="end">{t("Ends at", "Slutar")}</Label> + <Input id="end" name="end" type="datetime-local" required defaultValue={end} /> + </div> + </div> + <div className="field"> + <Label htmlFor="description">{t("Description", "Beskrivning")}</Label> + <Textarea + id="description" + name="description" + rows={3} + defaultValue={booking?.description ?? ""} + placeholder={t("Anything others should know?", "Något andra bör veta?")} + /> + </div> + <div className="space-y-4 border-t pt-5 text-sm leading-relaxed"> + <div className="flex gap-3"> + <Checkbox + id="terms" + checked={terms} + onCheckedChange={(value) => setTerms(value === true)} + /> + <label htmlFor="terms"> + {t("I accept the ", "Jag godkänner ")} + <a + className="text-primary underline" + href="https://docs.chalmers.it/bokningsvillkor.pdf" + target="_blank" + rel="noreferrer" + > + {t("booking terms and conditions", "bokningsvillkoren")} + </a> + . + </label> + </div> + <div className="flex gap-3"> + <Checkbox + id="privacy" + checked={privacy} + onCheckedChange={(value) => setPrivacy(value === true)} + /> + <div> + <label htmlFor="privacy"> + {t("I accept the privacy agreement. ", "Jag godkänner GDPR-avtalet. ")} + </label> + <Dialog> + <DialogTrigger asChild> + <button type="button" className="text-primary underline"> + {t("Read agreement", "Läs avtalet")} + </button> + </DialogTrigger> + <DialogContent> + <DialogHeader> + <DialogTitle>{t("Privacy agreement", "GDPR-avtal")}</DialogTitle> + <DialogDescription> + {t( + "How IT handles your booking information", + "Hur IT hanterar din bokningsinformation", + )} + </DialogDescription> + </DialogHeader> + <p className="whitespace-pre-line text-sm leading-6 max-h-[60vh] overflow-auto"> + {privacyText[language]} + </p> + </DialogContent> + </Dialog> + </div> + </div> + <div className="flex gap-3"> + <Checkbox + id="notification" + checked={notification} + onCheckedChange={(value) => setNotification(value === true)} + /> + <label htmlFor="notification"> + {t( + "I have notified Cubsec/Chalmers if this booking is for an event requiring notification.", + "Jag har anmält till Cubsec/Chalmers om bokningen gäller ett arrangemang som kräver anmälan.", + )}{" "} + <a + className="text-primary underline" + href="https://www.chalmers.se/utbildning/studera-hos-oss/studentliv/arrangemang-i-sektionslokaler/formular-for-anmalan-av-arrangemang/" + target="_blank" + rel="noreferrer" + > + {t("Notification form", "Anmälningsformulär")} + </a> + </label> + </div> + </div> + {error && ( + <p role="alert" className="rounded-lg bg-red-50 p-3 text-sm text-destructive"> + {error} + </p> + )} + <div className="flex justify-end gap-3"> + <Button type="button" variant="outline" onClick={() => void navigate({ to: "/" })}> + {t("Cancel", "Avbryt")} + </Button> + <Button disabled={mutation.isPending} type="submit"> + {mutation.isPending ? t("Saving…", "Sparar…") : t("Save booking", "Spara bokning")} + </Button> + </div> + </form> + ); +} diff --git a/frontend/src/components/event-calendar-icons.tsx b/frontend/src/components/event-calendar-icons.tsx new file mode 100644 index 0000000..d1efe3f --- /dev/null +++ b/frontend/src/components/event-calendar-icons.tsx @@ -0,0 +1,28 @@ +import { ChevronDownIcon, XIcon, ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; +import { cn } from "@/lib/utils"; + +export function EventCalendarPrevIcon() { + return <ChevronLeftIcon className="[[dir=rtl]_&]:rotate-180" />; +} + +export function EventCalendarNextIcon() { + return <ChevronRightIcon className="[[dir=rtl]_&]:rotate-180" />; +} + +const hoverIconClassName = + "text-muted-foreground group-hover:text-foreground group-focus-visible:text-foreground"; + +export function EventCalendarCloseIcon() { + return <XIcon className={`size-5 ${hoverIconClassName}`} />; +} + +export function EventCalendarExpanderIcon(props: { isExpanded: boolean }) { + return ( + <ChevronDownIcon + className={cn( + `size-4 m-px ${hoverIconClassName}`, + !props.isExpanded && "-rotate-90 [[dir=rtl]_&]:rotate-90", + )} + /> + ); +} diff --git a/frontend/src/components/event-calendar-toolbar.tsx b/frontend/src/components/event-calendar-toolbar.tsx new file mode 100644 index 0000000..d44b77b --- /dev/null +++ b/frontend/src/components/event-calendar-toolbar.tsx @@ -0,0 +1,82 @@ +import type { CalendarController } from "@fullcalendar/react"; +import { EventCalendarNextIcon, EventCalendarPrevIcon } from "@/components/event-calendar-icons"; +import { Button } from "@/components/ui/button"; +import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { cn } from "@/lib/utils"; + +export interface EventCalendarToolbarProps { + className?: string; + controller: CalendarController; + availableViews: string[]; + addButton?: { + isPrimary?: boolean; + text?: string; + hint?: string; + click?: () => void; + }; +} + +export function EventCalendarToolbar({ + className, + controller, + availableViews, + addButton, +}: EventCalendarToolbarProps) { + const buttons = controller.getButtonState(); + + return ( + <div className={cn("flex items-center justify-between flex-wrap gap-3", className)}> + <div className="flex items-center flex-wrap gap-3"> + {addButton && ( + <Button onClick={addButton.click} aria-label={addButton.hint}> + {addButton.text} + </Button> + )} + <Button + onClick={() => controller.today()} + aria-label={buttons.today.hint} + variant="outline" + > + {buttons.today.text} + </Button> + <div className="flex items-center"> + <Button + onClick={() => controller.prev()} + disabled={buttons.prev.isDisabled} + aria-label={buttons.prev.hint} + variant="ghost" + size="icon" + > + <EventCalendarPrevIcon /> + </Button> + <Button + onClick={() => controller.next()} + disabled={buttons.next.isDisabled} + aria-label={buttons.next.hint} + variant="ghost" + size="icon" + > + <EventCalendarNextIcon /> + </Button> + </div> + <h3 className="text-base font-semibold" aria-live="polite"> + {controller.view?.title} + </h3> + </div> + <Tabs value={controller.view?.type ?? availableViews[0]}> + <TabsList> + {availableViews.map((availableView) => ( + <TabsTrigger + key={availableView} + value={availableView} + onClick={() => controller.changeView(availableView)} + aria-label={buttons[availableView]?.hint} + > + {buttons[availableView]?.text} + </TabsTrigger> + ))} + </TabsList> + </Tabs> + </div> + ); +} diff --git a/frontend/src/components/event-calendar.tsx b/frontend/src/components/event-calendar.tsx new file mode 100644 index 0000000..f1322fc --- /dev/null +++ b/frontend/src/components/event-calendar.tsx @@ -0,0 +1,89 @@ +import { type CalendarOptions, useCalendarController } from "@fullcalendar/react"; +import dayGridPlugin from "@fullcalendar/react/daygrid"; +import interactionPlugin from "@fullcalendar/react/interaction"; +import listPlugin from "@fullcalendar/react/list"; +import multiMonthPlugin from "@fullcalendar/react/multimonth"; +import timeGridPlugin from "@fullcalendar/react/timegrid"; +import { EventCalendarToolbar } from "@/components/event-calendar-toolbar"; +import { EventCalendarViews } from "@/components/ui/event-calendar-views"; +import { EventCalendarCloseIcon } from "@/components/event-calendar-icons"; +import { cn } from "@/lib/utils"; + +const plugins = [dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin, multiMonthPlugin]; +const defaultAvailableViews = [ + "dayGridMonth", + "timeGridWeek", + "timeGridDay", + "listWeek", + "multiMonthYear", +]; +const navLinkDayClick = "timeGridDay"; +const navLinkWeekClick = "timeGridWeek"; + +export interface EventCalendarProps extends Omit< + CalendarOptions, + "class" | "className" | "headerToolbar" | "footerToolbar" +> { + className?: string; + availableViews?: string[]; + addButton?: { + isPrimary?: boolean; + text?: string; + hint?: string; + click?: () => void; + }; +} + +export function EventCalendar({ + availableViews = defaultAvailableViews, + addButton, + className, + height, + contentHeight, + direction, + plugins: userPlugins = [], + ...restOptions +}: EventCalendarProps) { + const controller = useCalendarController(); + + const hasBorderX = !(restOptions.borderlessX ?? restOptions.borderless); + const hasBorderTop = !(restOptions.borderlessTop ?? restOptions.borderless); + const hasBorderBottom = !(restOptions.borderlessBottom ?? restOptions.borderless); + const isHeightAuto = height === "auto" || contentHeight === "auto"; + + return ( + <div + className={cn( + className, + "flex flex-col bg-background", + hasBorderX && "border-x", + hasBorderTop && "border-t", + hasBorderBottom && "border-b", + hasBorderTop && hasBorderX && "rounded-t-lg", + hasBorderBottom && hasBorderX && "rounded-b-lg", + !isHeightAuto && "overflow-hidden", + )} + style={{ height }} + dir={direction === "rtl" ? "rtl" : undefined} + > + <EventCalendarToolbar + className="p-4" + controller={controller} + availableViews={availableViews} + addButton={addButton} + /> + <div className="grow min-h-0"> + <EventCalendarViews + controller={controller} + height={isHeightAuto ? "auto" : height !== undefined ? "100%" : contentHeight} + initialView={availableViews[0]} + navLinkDayClick={navLinkDayClick} + navLinkWeekClick={navLinkWeekClick} + plugins={[...plugins, ...userPlugins]} + popoverCloseContent={() => <EventCalendarCloseIcon />} + {...restOptions} + /> + </div> + </div> + ); +} diff --git a/frontend/src/components/feedback.tsx b/frontend/src/components/feedback.tsx new file mode 100644 index 0000000..c793388 --- /dev/null +++ b/frontend/src/components/feedback.tsx @@ -0,0 +1,30 @@ +import { AlertCircle, LoaderCircle } from "lucide-react"; +import { Button } from "@/components/ui/button"; +export function Loading() { + return ( + <output className="flex justify-center items-center gap-3 p-12 text-muted-foreground"> + <LoaderCircle className="size-5 animate-spin" /> + Loading… + </output> + ); +} +export function Failure({ error, retry }: { error: Error; retry?: () => void }) { + return ( + <div role="alert" className="my-6 rounded-xl border border-red-200 bg-red-50 p-6"> + <div className="flex gap-2 items-center font-medium"> + <AlertCircle className="size-5" /> + {error.message} + </div> + <div className="mt-3 flex gap-3"> + {retry && ( + <Button variant="outline" onClick={retry}> + Try again + </Button> + )} + <Button variant="outline" asChild> + <a href="/api/login">Sign in</a> + </Button> + </div> + </div> + ); +} diff --git a/frontend/src/components/ui/alert.tsx b/frontend/src/components/ui/alert.tsx new file mode 100644 index 0000000..f8feeb8 --- /dev/null +++ b/frontend/src/components/ui/alert.tsx @@ -0,0 +1,59 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; +import { cn } from "cn"; + +const alertVariants = cva( + "relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", + { + variants: { + variant: { + default: "bg-card text-card-foreground", + destructive: + "bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 [&>svg]:text-current", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +function Alert({ + className, + variant, + ...props +}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) { + return ( + <div + data-slot="alert" + role="alert" + className={cn(alertVariants({ variant }), className)} + {...props} + /> + ); +} + +function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( + <div + data-slot="alert-title" + className={cn("col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight", className)} + {...props} + /> + ); +} + +function AlertDescription({ className, ...props }: React.ComponentProps<"div">) { + return ( + <div + data-slot="alert-description" + className={cn( + "col-start-2 grid justify-items-start gap-1 text-sm text-muted-foreground [&_p]:leading-relaxed", + className, + )} + {...props} + /> + ); +} + +export { Alert, AlertTitle, AlertDescription }; diff --git a/frontend/src/components/ui/badge.tsx b/frontend/src/components/ui/badge.tsx new file mode 100644 index 0000000..681594e --- /dev/null +++ b/frontend/src/components/ui/badge.tsx @@ -0,0 +1,45 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; +import { cn } from "cn"; +import { Slot } from "radix-ui"; + +const badgeVariants = cva( + "inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90", + secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", + destructive: + "bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90", + outline: + "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + link: "text-primary underline-offset-4 [a&]:hover:underline", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +function Badge({ + className, + variant = "default", + asChild = false, + ...props +}: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & { asChild?: boolean }) { + const Comp = asChild ? Slot.Root : "span"; + + return ( + <Comp + data-slot="badge" + data-variant={variant} + className={cn(badgeVariants({ variant }), className)} + {...props} + /> + ); +} + +export { Badge, badgeVariants }; diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx new file mode 100644 index 0000000..73bce6f --- /dev/null +++ b/frontend/src/components/ui/button.tsx @@ -0,0 +1,61 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; +import { cn } from "cn"; +import { Slot } from "radix-ui"; + +const buttonVariants = cva( + "inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40", + outline: + "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", + secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2 has-[>svg]:px-3", + xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3", + sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5", + lg: "h-10 rounded-md px-6 has-[>svg]:px-4", + icon: "size-9", + "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3", + "icon-sm": "size-8", + "icon-lg": "size-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); + +function Button({ + className, + variant = "default", + size = "default", + asChild = false, + ...props +}: React.ComponentProps<"button"> & + VariantProps<typeof buttonVariants> & { + asChild?: boolean; + }) { + const Comp = asChild ? Slot.Root : "button"; + + return ( + <Comp + data-slot="button" + data-variant={variant} + data-size={size} + className={cn(buttonVariants({ variant, size, className }))} + {...props} + /> + ); +} + +export { Button, buttonVariants }; diff --git a/frontend/src/components/ui/card.tsx b/frontend/src/components/ui/card.tsx new file mode 100644 index 0000000..01b6263 --- /dev/null +++ b/frontend/src/components/ui/card.tsx @@ -0,0 +1,74 @@ +import * as React from "react"; +import { cn } from "cn"; + +function Card({ className, ...props }: React.ComponentProps<"div">) { + return ( + <div + data-slot="card" + className={cn( + "flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm", + className, + )} + {...props} + /> + ); +} + +function CardHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( + <div + data-slot="card-header" + className={cn( + "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6", + className, + )} + {...props} + /> + ); +} + +function CardTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( + <div + data-slot="card-title" + className={cn("leading-none font-semibold", className)} + {...props} + /> + ); +} + +function CardDescription({ className, ...props }: React.ComponentProps<"div">) { + return ( + <div + data-slot="card-description" + className={cn("text-sm text-muted-foreground", className)} + {...props} + /> + ); +} + +function CardAction({ className, ...props }: React.ComponentProps<"div">) { + return ( + <div + data-slot="card-action" + className={cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)} + {...props} + /> + ); +} + +function CardContent({ className, ...props }: React.ComponentProps<"div">) { + return <div data-slot="card-content" className={cn("px-6", className)} {...props} />; +} + +function CardFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( + <div + data-slot="card-footer" + className={cn("flex items-center px-6 [.border-t]:pt-6", className)} + {...props} + /> + ); +} + +export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent }; diff --git a/frontend/src/components/ui/checkbox.tsx b/frontend/src/components/ui/checkbox.tsx new file mode 100644 index 0000000..bc1b730 --- /dev/null +++ b/frontend/src/components/ui/checkbox.tsx @@ -0,0 +1,26 @@ +import * as React from "react"; +import { cn } from "cn"; +import { CheckIcon } from "lucide-react"; +import { Checkbox as CheckboxPrimitive } from "radix-ui"; + +function Checkbox({ className, ...props }: React.ComponentProps<typeof CheckboxPrimitive.Root>) { + return ( + <CheckboxPrimitive.Root + data-slot="checkbox" + className={cn( + "peer size-4 shrink-0 rounded-[4px] border border-input shadow-xs transition-shadow outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:bg-input/30 dark:aria-invalid:ring-destructive/40 dark:data-[state=checked]:bg-primary", + className, + )} + {...props} + > + <CheckboxPrimitive.Indicator + data-slot="checkbox-indicator" + className="grid place-content-center text-current transition-none" + > + <CheckIcon className="size-3.5" /> + </CheckboxPrimitive.Indicator> + </CheckboxPrimitive.Root> + ); +} + +export { Checkbox }; diff --git a/frontend/src/components/ui/dialog.tsx b/frontend/src/components/ui/dialog.tsx new file mode 100644 index 0000000..d631cd0 --- /dev/null +++ b/frontend/src/components/ui/dialog.tsx @@ -0,0 +1,144 @@ +"use client"; + +import * as React from "react"; +import { cn } from "cn"; +import { XIcon } from "lucide-react"; +import { Dialog as DialogPrimitive } from "radix-ui"; + +import { Button } from "@/components/ui/button"; + +function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) { + return <DialogPrimitive.Root data-slot="dialog" {...props} />; +} + +function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) { + return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />; +} + +function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) { + return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />; +} + +function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) { + return <DialogPrimitive.Close data-slot="dialog-close" {...props} />; +} + +function DialogOverlay({ + className, + ...props +}: React.ComponentProps<typeof DialogPrimitive.Overlay>) { + return ( + <DialogPrimitive.Overlay + data-slot="dialog-overlay" + className={cn( + "fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0", + className, + )} + {...props} + /> + ); +} + +function DialogContent({ + className, + children, + showCloseButton = true, + ...props +}: React.ComponentProps<typeof DialogPrimitive.Content> & { + showCloseButton?: boolean; +}) { + return ( + <DialogPortal data-slot="dialog-portal"> + <DialogOverlay /> + <DialogPrimitive.Content + data-slot="dialog-content" + className={cn( + "fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg", + className, + )} + {...props} + > + {children} + {showCloseButton && ( + <DialogPrimitive.Close + data-slot="dialog-close" + className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" + > + <XIcon /> + <span className="sr-only">Close</span> + </DialogPrimitive.Close> + )} + </DialogPrimitive.Content> + </DialogPortal> + ); +} + +function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( + <div + data-slot="dialog-header" + className={cn("flex flex-col gap-2 text-center sm:text-left", className)} + {...props} + /> + ); +} + +function DialogFooter({ + className, + showCloseButton = false, + children, + ...props +}: React.ComponentProps<"div"> & { + showCloseButton?: boolean; +}) { + return ( + <div + data-slot="dialog-footer" + className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)} + {...props} + > + {children} + {showCloseButton && ( + <DialogPrimitive.Close asChild> + <Button variant="outline">Close</Button> + </DialogPrimitive.Close> + )} + </div> + ); +} + +function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) { + return ( + <DialogPrimitive.Title + data-slot="dialog-title" + className={cn("text-lg leading-none font-semibold", className)} + {...props} + /> + ); +} + +function DialogDescription({ + className, + ...props +}: React.ComponentProps<typeof DialogPrimitive.Description>) { + return ( + <DialogPrimitive.Description + data-slot="dialog-description" + className={cn("text-sm text-muted-foreground", className)} + {...props} + /> + ); +} + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +}; diff --git a/frontend/src/components/ui/event-calendar-views.tsx b/frontend/src/components/ui/event-calendar-views.tsx new file mode 100644 index 0000000..4b7c5e7 --- /dev/null +++ b/frontend/src/components/ui/event-calendar-views.tsx @@ -0,0 +1,464 @@ +import FullCalendar, { type CalendarOptions, type DayCellInfo } from "@fullcalendar/react"; +import "@fullcalendar/react/skeleton.css"; +import { cn } from "@/lib/utils"; + +const xxsTextClass = "text-[0.6875rem]/[1.090909]"; + +const blockPointerResizerClass = "absolute hidden group-hover:block"; +const rowPointerResizerClass = `${blockPointerResizerClass} inset-y-0 w-2`; +const columnPointerResizerClass = `${blockPointerResizerClass} inset-x-0 h-2`; + +const blockTouchResizerClass = + "absolute size-2 border border-(--fc-event-color) bg-background rounded-full"; +const rowTouchResizerClass = `${blockTouchResizerClass} top-1/2 -mt-1`; +const columnTouchResizerClass = `${blockTouchResizerClass} left-1/2 -ml-1`; + +const tallDayCellBottomClass = "min-h-4"; +const getShortDayCellBottomClass = (info: DayCellInfo) => cn(!info.isNarrow && "min-h-px"); + +const dayRowCommonClasses: CalendarOptions = { + /* Day Row > List-Item Event + ----------------------------------------------------------------------------------------------- */ + + listItemEventClass: (info) => cn("mb-px p-px rounded-sm", info.isNarrow ? "mx-px" : "mx-0.5"), + listItemEventBeforeClass: (info) => cn("border-4", info.isNarrow ? "ms-0.5" : "ms-1"), + listItemEventInnerClass: (info) => (info.isNarrow ? `py-px ${xxsTextClass}` : "py-0.5 text-xs"), + listItemEventTimeClass: (info) => + cn(info.isNarrow ? "ps-0.5" : "ps-1", "whitespace-nowrap overflow-hidden shrink-1"), + listItemEventTitleClass: (info) => + cn(info.isNarrow ? "px-0.5" : "px-1", "font-bold whitespace-nowrap overflow-hidden shrink-100"), + + /* Day Row > Row Event + ----------------------------------------------------------------------------------------------- */ + + rowEventClass: (info) => cn(info.isStart && "ms-px", info.isEnd && "me-px"), + rowEventInnerClass: (info) => cn(info.isNarrow ? "py-px" : "py-0.5"), + + /* Day Row > More-Link + ----------------------------------------------------------------------------------------------- */ + + rowMoreLinkClass: (info) => + cn( + "mb-px border rounded-sm hover:bg-foreground/5", + info.isNarrow ? "mx-px border-primary" : "mx-0.5 border-transparent", + ), + rowMoreLinkInnerClass: (info) => + info.isNarrow ? `px-0.5 py-px ${xxsTextClass}` : "px-1 py-0.5 text-xs", +}; + +export type EventCalendarViewProps = CalendarOptions & + Required<Pick<CalendarOptions, "popoverCloseContent">>; // ensure callers define icons + +export function EventCalendarViews({ views: userViews, ...restOptions }: EventCalendarViewProps) { + return ( + <FullCalendar + /* Abstract Event + ----------------------------------------------------------------------------------------- */ + + eventShortHeight={50} + eventColor="var(--primary)" + eventContrastColor="var(--primary-foreground)" + eventClass={(info) => + cn( + info.isSelected + ? ["outline-3", info.isDragging ? "shadow-lg" : "shadow-md"] + : "focus-visible:outline-3", + "outline-ring/50", + ) + } + + /* Background Event + ----------------------------------------------------------------------------------------- */ + + backgroundEventColor="var(--chart-3)" + backgroundEventClass="not-print:bg-[color-mix(in_oklab,var(--fc-event-color)_10%,transparent)] print:border-1 print:border-(--fc-event-color)" + backgroundEventTitleClass={(info) => + cn( + "opacity-50 italic", + info.isNarrow ? `px-1 py-1.5 ${xxsTextClass}` : "px-2 py-2.5 text-xs", + ) + } + + /* List-Item Event + ----------------------------------------------------------------------------------------- */ + + listItemEventClass={(info) => + cn("items-center", info.isSelected ? "bg-foreground/5" : "hover:bg-foreground/5") + } + listItemEventBeforeClass="rounded-full border-(--fc-event-color)" + listItemEventInnerClass="flex flex-row items-center" + + /* Block Event + ----------------------------------------------------------------------------------------- */ + + blockEventClass={(info) => + cn( + "group relative border-transparent print:border-(--fc-event-color) bg-(--fc-event-color) hover:bg-[color-mix(in_oklab,var(--fc-event-color)_92%,var(--fc-event-contrast-color))] print:bg-white", + info.isInteractive && + "active:bg-[color-mix(in_oklab,var(--fc-event-color)_85%,var(--fc-event-contrast-color))]", + !info.isSelected && info.isDragging && "opacity-75", + ) + } + blockEventInnerClass="text-(--fc-event-contrast-color) print:text-black" + blockEventTimeClass="whitespace-nowrap overflow-hidden" + blockEventTitleClass="whitespace-nowrap overflow-hidden" + + /* Row Event + ----------------------------------------------------------------------------------------- */ + + rowEventClass={(info) => + cn( + "mb-px border-y", + info.isStart ? "border-s rounded-s-sm" : !info.isNarrow && "ms-2", + info.isEnd ? "border-e rounded-e-sm" : !info.isNarrow && "me-2", + ) + } + rowEventBeforeClass={(info) => + info.isStartResizable + ? cn(info.isSelected ? rowTouchResizerClass : rowPointerResizerClass, "-start-1") + : !info.isStart && !info.isNarrow + ? "absolute -start-2 w-2 -top-px -bottom-px" + : "" + } + rowEventBeforeContent={ + (info) => + !info.isStart && !info.isNarrow ? ( + filledRightTriangle( + "size-full rotate-180 [[dir=rtl]_&]:rotate-0 text-(--fc-event-color)", + ) + ) : ( + <></> + ) // HACK for React vdom + } + rowEventAfterClass={(info) => + info.isEndResizable + ? cn(info.isSelected ? rowTouchResizerClass : rowPointerResizerClass, "-end-1") + : !info.isEnd && !info.isNarrow + ? "absolute -end-2 w-2 -top-px -bottom-px" + : "" + } + rowEventAfterContent={ + (info) => + !info.isEnd && !info.isNarrow ? ( + filledRightTriangle("size-full [[dir=rtl]_&]:rotate-180 text-(--fc-event-color)") + ) : ( + <></> + ) // HACK for React vdom + } + rowEventInnerClass={(info) => + cn("flex flex-row items-center", info.isNarrow ? xxsTextClass : "text-xs") + } + rowEventTimeClass={(info) => cn("font-bold shrink-1", info.isNarrow ? "ps-0.5" : "ps-1")} + rowEventTitleClass={(info) => cn("shrink-100", info.isNarrow ? "px-0.5" : "px-1")} + + /* Column Event + ----------------------------------------------------------------------------------------- */ + + columnEventTitleSticky={false} + columnEventClass={(info) => + cn( + "border-x ring ring-background", + info.isStart && "border-t rounded-t-sm", + info.isEnd && "mb-px border-b rounded-b-sm", + ) + } + columnEventBeforeClass={(info) => + cn( + info.isStartResizable && [ + info.isSelected ? columnTouchResizerClass : columnPointerResizerClass, + "-top-1", + ], + ) + } + columnEventAfterClass={(info) => + cn( + info.isEndResizable && [ + info.isSelected ? columnTouchResizerClass : columnPointerResizerClass, + "-bottom-1", + ], + ) + } + columnEventInnerClass={(info) => + cn( + "flex", + info.isShort + ? "flex-row items-center p-1 gap-1" + : ["flex-col", info.isNarrow ? "px-1 py-0.5" : "px-2 py-1"], + info.isShort || info.isNarrow ? xxsTextClass : "text-xs", + ) + } + columnEventTimeClass={(info) => + cn("order-1 shrink-100", !info.isShort && (info.isNarrow ? "pb-0.5" : "pb-1")) + } + columnEventTitleClass={(info) => + cn("shrink-1", !info.isShort && (info.isNarrow ? "py-0.5" : "py-1")) + } + + /* More-Link + ----------------------------------------------------------------------------------------- */ + + moreLinkClass="focus-visible:outline-3 outline-ring/50" + moreLinkInnerClass="whitespace-nowrap overflow-hidden" + columnMoreLinkClass="mb-px border border-transparent print:border-black rounded-sm bg-[color-mix(in_oklab,var(--foreground)_10%,var(--background))] hover:bg-[color-mix(in_oklab,var(--foreground)_13%,var(--background))] print:bg-white ring ring-background" + columnMoreLinkInnerClass={(info) => (info.isNarrow ? `p-0.5 ${xxsTextClass}` : "p-1 text-xs")} + + /* Day Header + ----------------------------------------------------------------------------------------- */ + + dayHeaderAlign="center" + dayHeaderClass={(info) => + cn( + "justify-center", + info.isMajor && "border border-foreground/20", + info.isDisabled && !info.inPopover && "bg-foreground/3", + ) + } + dayHeaderInnerClass="group mt-2 mx-2 flex flex-col items-center outline-none" + dayHeaderContent={(info) => ( + <> + {info.weekdayText && ( + <div className="text-xs uppercase text-muted-foreground">{info.weekdayText}</div> + )} + {info.dayNumberText && ( + <div + className={cn( + "m-0.5 rounded-full flex flex-row items-center justify-center", + info.isNarrow ? "size-7 text-base" : "size-8 text-lg", + info.isToday + ? ["bg-chart-1/50", info.hasNavLink && "group-hover:bg-chart-1/60"] + : info.hasNavLink && "group-hover:bg-foreground/5", + info.hasNavLink && "group-focus-visible:outline-3 outline-ring/50", + )} + > + {info.dayNumberText} + </div> + )} + </> + )} + + /* Day Cell + ----------------------------------------------------------------------------------------- */ + + dayCellClass={(info) => + cn("border", info.isMajor && "border-foreground/20", info.isDisabled && "bg-foreground/3") + } + dayCellTopClass={(info) => + cn("flex flex-row", info.isNarrow ? "justify-end min-h-px" : "justify-center min-h-0.5") + } + dayCellTopInnerClass={(info) => + cn( + "flex flex-row items-center justify-center whitespace-nowrap rounded-full", + info.isNarrow ? `m-px h-5 ${xxsTextClass}` : "m-1.5 h-6 text-sm", + info.text === info.dayNumberText + ? info.isNarrow + ? "w-5" + : "w-6" + : info.isNarrow + ? "px-1" + : "px-2", + info.isToday + ? [ + "bg-chart-1/50", + info.hasNavLink && "hover:bg-chart-1/60 focus-visible:outline-3 outline-ring/50", + ] + : info.hasNavLink && "hover:bg-foreground/5", + info.isOther && "text-muted-foreground", + info.monthText && "font-bold", + ) + } + dayCellInnerClass={(info) => cn(info.inPopover && "p-2")} + + /* Popover + ----------------------------------------------------------------------------------------- */ + + popoverFormat={{ day: "numeric", weekday: "short" }} + popoverClass="border rounded-md overflow-hidden shadow-lg m-1 bg-popover text-popover-foreground min-w-60" + popoverCloseClass="group absolute top-2 end-2 size-8 rounded-full items-center justify-center hover:bg-foreground/5 focus-visible:outline-3 outline-ring/50" + + /* Lane + ----------------------------------------------------------------------------------------- */ + + dayLaneClass={(info) => + cn("border", info.isMajor && "border-foreground/20", info.isDisabled && "bg-foreground/3") + } + dayLaneInnerClass={(info) => + info.isStack ? "m-1" : info.isNarrow ? "mx-px" : "ms-0.5 me-[2.5%]" + } + slotLaneClass={(info) => cn("border", info.isMinor && "border-dotted")} + + /* List Day + ----------------------------------------------------------------------------------------- */ + + listDayFormat={{ day: "numeric" }} + listDayAltFormat={{ month: "short", weekday: "short", forceCommas: true }} + listDayClass={(info) => cn(!info.isLast && "border-b", "flex flex-row items-start")} + listDayHeaderClass="p-2 shrink-0 w-1/3 max-w-44 min-h-9 flex flex-row items-center gap-2" + listDayHeaderInnerClass={(info) => + cn( + !info.level + ? [ + "h-9 rounded-full flex flex-row items-center text-lg", + info.text === info.dayNumberText ? "w-9 justify-center" : "px-3", + info.isToday + ? [ + "bg-chart-1/50", + info.hasNavLink && + "hover:bg-chart-1/60 focus-visible:outline-3 outline-ring/50", + ] + : info.hasNavLink && "hover:bg-foreground/5", + ] + : ["text-xs uppercase", info.hasNavLink && "hover:underline"], + ) + } + listDayBodyClass="grow min-w-0 py-2 gap-1" + + /* Single Month (in Multi-Month) + ----------------------------------------------------------------------------------------- */ + + singleMonthClass={(info) => + cn( + info.multiMonthColumns > 1 && "m-4", + info.multiMonthColumns === 1 && !info.isLast && "border-b", + ) + } + singleMonthHeaderClass={(info) => + cn(info.multiMonthColumns > 1 ? "pb-2" : "py-1 border-b bg-background", "items-center") + } + singleMonthHeaderInnerClass={(info) => + cn("px-3 py-1 rounded-full text-base font-bold", info.hasNavLink && "hover:bg-foreground/5") + } + + /* Misc Table + ----------------------------------------------------------------------------------------- */ + + tableHeaderClass="bg-background" + fillerClass={(info) => cn("opacity-50 border", info.inTableHeader && "border-transparent")} + dayNarrowWidth={100} + dayHeaderRowClass="border" + dayRowClass="border" + + /* Misc Content + ----------------------------------------------------------------------------------------- */ + + navLinkClass="focus-visible:outline-3 outline-ring/50" + inlineWeekNumberClass={(info) => + cn( + "absolute flex flex-row items-center whitespace-nowrap bg-foreground/10", + info.isNarrow + ? `top-0.5 start-0 my-px h-4 pe-1 rounded-e-full ${xxsTextClass}` + : "top-1.5 start-1 h-6 px-2 rounded-full text-sm", + info.hasNavLink && "hover:bg-foreground/20 focus-visible:outline-3 outline-ring/50", + ) + } + nonBusinessHoursClass="bg-foreground/3" + highlightClass="bg-chart-1/20" + nowIndicatorLineClass="-m-px border-1 border-destructive" + nowIndicatorDotClass="-m-[6px] border-6 border-destructive size-0 rounded-full ring-2 ring-background" + + /* View-Specific Options + ----------------------------------------------------------------------------------------- */ + + views={{ + ...userViews, + dayGrid: { + ...dayRowCommonClasses, + dayCellBottomClass: getShortDayCellBottomClass, + ...userViews?.dayGrid, + }, + multiMonth: { + ...dayRowCommonClasses, + dayCellBottomClass: getShortDayCellBottomClass, + tableBodyClass: (info) => + cn(info.multiMonthColumns > 1 && "border rounded-sm overflow-hidden"), + dayHeaderInnerClass: (info) => cn(!info.inPopover && "mb-2"), + dayHeaderDividerClass: (info) => cn(info.multiMonthColumns === 1 && "border-b"), + ...userViews?.multiMonth, + }, + timeGrid: { + ...dayRowCommonClasses, + dayCellBottomClass: tallDayCellBottomClass, + + /* TimeGrid > Week Number Header + ------------------------------------------------------------------------------------- */ + + weekNumberHeaderClass: "items-center justify-end", + weekNumberHeaderInnerClass: (info) => + cn( + "ms-1 my-2 flex flex-row items-center rounded-full bg-foreground/10", + info.options.dayMinWidth !== undefined && "me-1", + info.isNarrow ? "h-5 px-1.5 text-xs" : "h-6 px-2 text-sm", + info.hasNavLink && "hover:bg-foreground/20 focus-visible:outline-3 outline-ring/50", + ), + + /* TimeGrid > All-Day Header + ------------------------------------------------------------------------------------- */ + + allDayHeaderClass: "items-center justify-end", + allDayHeaderInnerClass: (info) => + cn("m-2 text-end", info.isNarrow ? xxsTextClass : "text-sm"), + allDayDividerClass: "border-b", + + /* TimeGrid > Slot Header + ------------------------------------------------------------------------------------- */ + + slotHeaderClass: (info) => + cn("w-2 self-end justify-end", "border", info.isMinor && "border-dotted"), + slotHeaderInnerClass: (info) => + cn( + "relative ms-2 me-3 my-2", + info.isNarrow ? `-top-4 ${xxsTextClass}` : "-top-5 text-sm", + info.isFirst && "hidden", + ), + slotHeaderDividerClass: (info) => + cn( + "border-e", + info.inTableHeader && info.options.dayMinWidth === undefined && "border-transparent", + ), + + ...userViews?.timeGrid, + }, + list: { + /* List-View > List-Item Event + ------------------------------------------------------------------------------------- */ + + listItemEventClass: "group p-2 rounded-s-full gap-2", + listItemEventBeforeClass: "mx-2 border-5", + listItemEventInnerClass: "gap-2 text-sm", + listItemEventTimeClass: + "shrink-0 w-1/2 max-w-40 whitespace-nowrap overflow-hidden text-ellipsis", + listItemEventTitleClass: (info) => + cn( + "grow min-w-0 whitespace-nowrap overflow-hidden", + info.event.url && "group-hover:underline", + ), + + /* No-Events Screen + ------------------------------------------------------------------------------------- */ + + noEventsClass: "grow flex flex-col items-center justify-center", + noEventsInnerClass: "py-15", + + ...userViews?.list, + }, + }} + + {...restOptions} + /> + ); +} + +/* SVGs +------------------------------------------------------------------------------------------------- */ + +function filledRightTriangle(className?: string) { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 800 2200" + preserveAspectRatio="none" + className={className} + > + <polygon points="0,0 66,0 800,1100 66,2200 0,2200" fill="currentColor" /> + </svg> + ); +} diff --git a/frontend/src/components/ui/input.tsx b/frontend/src/components/ui/input.tsx new file mode 100644 index 0000000..86342c1 --- /dev/null +++ b/frontend/src/components/ui/input.tsx @@ -0,0 +1,20 @@ +import * as React from "react"; +import { cn } from "cn"; + +function Input({ className, type, ...props }: React.ComponentProps<"input">) { + return ( + <input + type={type} + data-slot="input" + className={cn( + "h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30", + "focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50", + "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40", + className, + )} + {...props} + /> + ); +} + +export { Input }; diff --git a/frontend/src/components/ui/label.tsx b/frontend/src/components/ui/label.tsx new file mode 100644 index 0000000..fbe68a5 --- /dev/null +++ b/frontend/src/components/ui/label.tsx @@ -0,0 +1,18 @@ +import * as React from "react"; +import { cn } from "cn"; +import { Label as LabelPrimitive } from "radix-ui"; + +function Label({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) { + return ( + <LabelPrimitive.Root + data-slot="label" + className={cn( + "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50", + className, + )} + {...props} + /> + ); +} + +export { Label }; diff --git a/frontend/src/components/ui/select.tsx b/frontend/src/components/ui/select.tsx new file mode 100644 index 0000000..0218de4 --- /dev/null +++ b/frontend/src/components/ui/select.tsx @@ -0,0 +1,174 @@ +"use client"; + +import * as React from "react"; +import { cn } from "cn"; +import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"; +import { Select as SelectPrimitive } from "radix-ui"; + +function Select({ ...props }: React.ComponentProps<typeof SelectPrimitive.Root>) { + return <SelectPrimitive.Root data-slot="select" {...props} />; +} + +function SelectGroup({ ...props }: React.ComponentProps<typeof SelectPrimitive.Group>) { + return <SelectPrimitive.Group data-slot="select-group" {...props} />; +} + +function SelectValue({ ...props }: React.ComponentProps<typeof SelectPrimitive.Value>) { + return <SelectPrimitive.Value data-slot="select-value" {...props} />; +} + +function SelectTrigger({ + className, + size = "default", + children, + ...props +}: React.ComponentProps<typeof SelectPrimitive.Trigger> & { + size?: "sm" | "default"; +}) { + return ( + <SelectPrimitive.Trigger + data-slot="select-trigger" + data-size={size} + className={cn( + "flex w-fit items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground", + className, + )} + {...props} + > + {children} + <SelectPrimitive.Icon asChild> + <ChevronDownIcon className="size-4 opacity-50" /> + </SelectPrimitive.Icon> + </SelectPrimitive.Trigger> + ); +} + +function SelectContent({ + className, + children, + position = "item-aligned", + align = "center", + ...props +}: React.ComponentProps<typeof SelectPrimitive.Content>) { + return ( + <SelectPrimitive.Portal> + <SelectPrimitive.Content + data-slot="select-content" + className={cn( + "relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95", + position === "popper" && + "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", + className, + )} + position={position} + align={align} + {...props} + > + <SelectScrollUpButton /> + <SelectPrimitive.Viewport + className={cn( + "p-1", + position === "popper" && + "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1", + )} + > + {children} + </SelectPrimitive.Viewport> + <SelectScrollDownButton /> + </SelectPrimitive.Content> + </SelectPrimitive.Portal> + ); +} + +function SelectLabel({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Label>) { + return ( + <SelectPrimitive.Label + data-slot="select-label" + className={cn("px-2 py-1.5 text-xs text-muted-foreground", className)} + {...props} + /> + ); +} + +function SelectItem({ + className, + children, + ...props +}: React.ComponentProps<typeof SelectPrimitive.Item>) { + return ( + <SelectPrimitive.Item + data-slot="select-item" + className={cn( + "relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2", + className, + )} + {...props} + > + <span + data-slot="select-item-indicator" + className="absolute right-2 flex size-3.5 items-center justify-center" + > + <SelectPrimitive.ItemIndicator> + <CheckIcon className="size-4" /> + </SelectPrimitive.ItemIndicator> + </span> + <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText> + </SelectPrimitive.Item> + ); +} + +function SelectSeparator({ + className, + ...props +}: React.ComponentProps<typeof SelectPrimitive.Separator>) { + return ( + <SelectPrimitive.Separator + data-slot="select-separator" + className={cn("pointer-events-none -mx-1 my-1 h-px bg-border", className)} + {...props} + /> + ); +} + +function SelectScrollUpButton({ + className, + ...props +}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) { + return ( + <SelectPrimitive.ScrollUpButton + data-slot="select-scroll-up-button" + className={cn("flex cursor-default items-center justify-center py-1", className)} + {...props} + > + <ChevronUpIcon className="size-4" /> + </SelectPrimitive.ScrollUpButton> + ); +} + +function SelectScrollDownButton({ + className, + ...props +}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) { + return ( + <SelectPrimitive.ScrollDownButton + data-slot="select-scroll-down-button" + className={cn("flex cursor-default items-center justify-center py-1", className)} + {...props} + > + <ChevronDownIcon className="size-4" /> + </SelectPrimitive.ScrollDownButton> + ); +} + +export { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectScrollDownButton, + SelectScrollUpButton, + SelectSeparator, + SelectTrigger, + SelectValue, +}; diff --git a/frontend/src/components/ui/separator.tsx b/frontend/src/components/ui/separator.tsx new file mode 100644 index 0000000..c51c144 --- /dev/null +++ b/frontend/src/components/ui/separator.tsx @@ -0,0 +1,27 @@ +"use client"; + +import * as React from "react"; +import { cn } from "cn"; +import { Separator as SeparatorPrimitive } from "radix-ui"; + +function Separator({ + className, + orientation = "horizontal", + decorative = true, + ...props +}: React.ComponentProps<typeof SeparatorPrimitive.Root>) { + return ( + <SeparatorPrimitive.Root + data-slot="separator" + decorative={decorative} + orientation={orientation} + className={cn( + "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px", + className, + )} + {...props} + /> + ); +} + +export { Separator }; diff --git a/frontend/src/components/ui/skeleton.tsx b/frontend/src/components/ui/skeleton.tsx new file mode 100644 index 0000000..ce70ac2 --- /dev/null +++ b/frontend/src/components/ui/skeleton.tsx @@ -0,0 +1,13 @@ +import { cn } from "cn"; + +function Skeleton({ className, ...props }: React.ComponentProps<"div">) { + return ( + <div + data-slot="skeleton" + className={cn("animate-pulse rounded-md bg-accent", className)} + {...props} + /> + ); +} + +export { Skeleton }; diff --git a/frontend/src/components/ui/table.tsx b/frontend/src/components/ui/table.tsx new file mode 100644 index 0000000..c7c83dd --- /dev/null +++ b/frontend/src/components/ui/table.tsx @@ -0,0 +1,89 @@ +import * as React from "react"; +import { cn } from "cn"; + +function Table({ className, ...props }: React.ComponentProps<"table">) { + return ( + <div data-slot="table-container" className="relative w-full overflow-x-auto"> + <table + data-slot="table" + className={cn("w-full caption-bottom text-sm", className)} + {...props} + /> + </div> + ); +} + +function TableHeader({ className, ...props }: React.ComponentProps<"thead">) { + return <thead data-slot="table-header" className={cn("[&_tr]:border-b", className)} {...props} />; +} + +function TableBody({ className, ...props }: React.ComponentProps<"tbody">) { + return ( + <tbody + data-slot="table-body" + className={cn("[&_tr:last-child]:border-0", className)} + {...props} + /> + ); +} + +function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) { + return ( + <tfoot + data-slot="table-footer" + className={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className)} + {...props} + /> + ); +} + +function TableRow({ className, ...props }: React.ComponentProps<"tr">) { + return ( + <tr + data-slot="table-row" + className={cn( + "border-b transition-colors hover:bg-muted/50 has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted", + className, + )} + {...props} + /> + ); +} + +function TableHead({ className, ...props }: React.ComponentProps<"th">) { + return ( + <th + data-slot="table-head" + className={cn( + "h-10 px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", + className, + )} + {...props} + /> + ); +} + +function TableCell({ className, ...props }: React.ComponentProps<"td">) { + return ( + <td + data-slot="table-cell" + className={cn( + "p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", + className, + )} + {...props} + /> + ); +} + +function TableCaption({ className, ...props }: React.ComponentProps<"caption">) { + return ( + <caption + data-slot="table-caption" + className={cn("mt-4 text-sm text-muted-foreground", className)} + {...props} + /> + ); +} + +export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }; diff --git a/frontend/src/components/ui/tabs.tsx b/frontend/src/components/ui/tabs.tsx new file mode 100644 index 0000000..625b2cd --- /dev/null +++ b/frontend/src/components/ui/tabs.tsx @@ -0,0 +1,78 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; +import { cn } from "cn"; +import { Tabs as TabsPrimitive } from "radix-ui"; + +function Tabs({ + className, + orientation = "horizontal", + ...props +}: React.ComponentProps<typeof TabsPrimitive.Root>) { + return ( + <TabsPrimitive.Root + data-slot="tabs" + data-orientation={orientation} + orientation={orientation} + className={cn("group/tabs flex gap-2 data-[orientation=horizontal]:flex-col", className)} + {...props} + /> + ); +} + +const tabsListVariants = cva( + "group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-[orientation=horizontal]/tabs:h-9 group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col data-[variant=line]:rounded-none", + { + variants: { + variant: { + default: "bg-muted", + line: "gap-1 bg-transparent", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +function TabsList({ + className, + variant = "default", + ...props +}: React.ComponentProps<typeof TabsPrimitive.List> & VariantProps<typeof tabsListVariants>) { + return ( + <TabsPrimitive.List + data-slot="tabs-list" + data-variant={variant} + className={cn(tabsListVariants({ variant }), className)} + {...props} + /> + ); +} + +function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Trigger>) { + return ( + <TabsPrimitive.Trigger + data-slot="tabs-trigger" + className={cn( + "relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none dark:text-muted-foreground dark:hover:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent", + "data-[state=active]:bg-background data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 dark:data-[state=active]:text-foreground", + "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100", + className, + )} + {...props} + /> + ); +} + +function TabsContent({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Content>) { + return ( + <TabsPrimitive.Content + data-slot="tabs-content" + className={cn("flex-1 outline-none", className)} + {...props} + /> + ); +} + +export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }; diff --git a/frontend/src/components/ui/textarea.tsx b/frontend/src/components/ui/textarea.tsx new file mode 100644 index 0000000..741a012 --- /dev/null +++ b/frontend/src/components/ui/textarea.tsx @@ -0,0 +1,17 @@ +import * as React from "react"; +import { cn } from "cn"; + +function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { + return ( + <textarea + data-slot="textarea" + className={cn( + "flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:ring-destructive/40", + className, + )} + {...props} + /> + ); +} + +export { Textarea }; diff --git a/frontend/src/generated/graphql.ts b/frontend/src/generated/graphql.ts new file mode 100644 index 0000000..4d25b7f --- /dev/null +++ b/frontend/src/generated/graphql.ts @@ -0,0 +1,113 @@ +/** Internal type. DO NOT USE DIRECTLY. */ +type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] }; +/** Internal type. DO NOT USE DIRECTLY. */ +export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }; +import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'; +export type InputEvent = { + id?: string | null | undefined; + start: string; + end: string; + description?: string | null | undefined; + title: string; + created_at?: string | null | undefined; + updated_at?: string | null | undefined; + room: Array<Room>; + phone?: string | null | undefined; + booked_as: string; + booking_terms: boolean; +}; + +export type Room = + | 'BIG_HUB' + | 'GROUP_ROOM' + | 'CTC' + | 'THE_CLOUD'; + +export type InputRule = { + id?: string | null | undefined; + day_mask?: number | null | undefined; + start_date: string; + end_date: string; + start_time: string; + end_time: string; + description: string; + allow?: boolean | null | undefined; + priority?: number | null | undefined; + title: string; + created_at?: string | null | undefined; + updated_at?: string | null | undefined; + room: Array<Room>; +}; + +export type BookingFragment = { id: string | null, title: string, description: string | null, start: string, end: string, room: Array<Room>, phone: string | null, booked_as: string, booked_by: string, created_at: string | null, updated_at: string | null }; + +export type CurrentUserQueryVariables = Exact<{ [key: string]: never; }>; + + +export type CurrentUserQuery = { user: { sub: string | null, cid: string | null, name: string | null, nickname: string | null, locale: string | null, groups: Array<string | null> | null, is_admin: boolean | null } | null }; + +export type CalendarQueryVariables = Exact<{ + from: string; + to: string; +}>; + + +export type CalendarQuery = { eventsFT: Array<{ id: string | null, title: string, description: string | null, start: string, end: string, room: Array<Room>, phone: string | null, booked_as: string, booked_by: string, created_at: string | null, updated_at: string | null } | null> | null, illegalSlots: Array<{ start: string, end: string, title: string, description: string | null, room: Array<Room> }> | null }; + +export type BookingDetailQueryVariables = Exact<{ + id: string; +}>; + + +export type BookingDetailQuery = { event: { id: string | null, title: string, description: string | null, start: string, end: string, room: Array<Room>, phone: string | null, booked_as: string, booked_by: string, created_at: string | null, updated_at: string | null } | null }; + +export type CreateBookingMutationVariables = Exact<{ + event: InputEvent; +}>; + + +export type CreateBookingMutation = { createEvent: { en: string, sv: string } | null }; + +export type UpdateBookingMutationVariables = Exact<{ + event: InputEvent; +}>; + + +export type UpdateBookingMutation = { editEvent: { en: string, sv: string } | null }; + +export type DeleteBookingMutationVariables = Exact<{ + id: string; +}>; + + +export type DeleteBookingMutation = { deleteEvent: { en: string, sv: string } | null }; + +export type RulesQueryVariables = Exact<{ [key: string]: never; }>; + + +export type RulesQuery = { rules: Array<{ id: string | null, created_at: string | null, updated_at: string | null, title: string | null, description: string | null, day_mask: number | null, start_date: string | null, end_date: string | null, start_time: string | null, end_time: string | null, priority: number | null, allow: boolean | null, room: Array<Room> | null } | null> | null }; + +export type CreateRuleMutationVariables = Exact<{ + rule: InputRule; +}>; + + +export type CreateRuleMutation = { createRule: { en: string, sv: string } | null }; + +export type DeleteRuleMutationVariables = Exact<{ + id: string; +}>; + + +export type DeleteRuleMutation = { deleteRule: { en: string, sv: string } | null }; + +export const BookingFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"Booking"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Event"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"end"}},{"kind":"Field","name":{"kind":"Name","value":"room"}},{"kind":"Field","name":{"kind":"Name","value":"phone"}},{"kind":"Field","name":{"kind":"Name","value":"booked_as"}},{"kind":"Field","name":{"kind":"Name","value":"booked_by"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}}]}}]} as unknown as DocumentNode<BookingFragment, unknown>; +export const CurrentUserDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CurrentUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"sub"}},{"kind":"Field","name":{"kind":"Name","value":"cid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"nickname"}},{"kind":"Field","name":{"kind":"Name","value":"locale"}},{"kind":"Field","name":{"kind":"Name","value":"groups"}},{"kind":"Field","name":{"kind":"Name","value":"is_admin"}}]}}]}}]} as unknown as DocumentNode<CurrentUserQuery, CurrentUserQueryVariables>; +export const CalendarDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Calendar"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"from"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"to"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"eventsFT"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"from"},"value":{"kind":"Variable","name":{"kind":"Name","value":"from"}}},{"kind":"Argument","name":{"kind":"Name","value":"to"},"value":{"kind":"Variable","name":{"kind":"Name","value":"to"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"Booking"}}]}},{"kind":"Field","name":{"kind":"Name","value":"illegalSlots"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"from"},"value":{"kind":"Variable","name":{"kind":"Name","value":"from"}}},{"kind":"Argument","name":{"kind":"Name","value":"to"},"value":{"kind":"Variable","name":{"kind":"Name","value":"to"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"end"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"room"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"Booking"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Event"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"end"}},{"kind":"Field","name":{"kind":"Name","value":"room"}},{"kind":"Field","name":{"kind":"Name","value":"phone"}},{"kind":"Field","name":{"kind":"Name","value":"booked_as"}},{"kind":"Field","name":{"kind":"Name","value":"booked_by"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}}]}}]} as unknown as DocumentNode<CalendarQuery, CalendarQueryVariables>; +export const BookingDetailDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"BookingDetail"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"event"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"Booking"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"Booking"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Event"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"end"}},{"kind":"Field","name":{"kind":"Name","value":"room"}},{"kind":"Field","name":{"kind":"Name","value":"phone"}},{"kind":"Field","name":{"kind":"Name","value":"booked_as"}},{"kind":"Field","name":{"kind":"Name","value":"booked_by"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}}]}}]} as unknown as DocumentNode<BookingDetailQuery, BookingDetailQueryVariables>; +export const CreateBookingDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateBooking"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"event"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"InputEvent"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createEvent"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"event"},"value":{"kind":"Variable","name":{"kind":"Name","value":"event"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"en"}},{"kind":"Field","name":{"kind":"Name","value":"sv"}}]}}]}}]} as unknown as DocumentNode<CreateBookingMutation, CreateBookingMutationVariables>; +export const UpdateBookingDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateBooking"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"event"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"InputEvent"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"editEvent"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"event"},"value":{"kind":"Variable","name":{"kind":"Name","value":"event"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"en"}},{"kind":"Field","name":{"kind":"Name","value":"sv"}}]}}]}}]} as unknown as DocumentNode<UpdateBookingMutation, UpdateBookingMutationVariables>; +export const DeleteBookingDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteBooking"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteEvent"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"en"}},{"kind":"Field","name":{"kind":"Name","value":"sv"}}]}}]}}]} as unknown as DocumentNode<DeleteBookingMutation, DeleteBookingMutationVariables>; +export const RulesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Rules"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"rules"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"day_mask"}},{"kind":"Field","name":{"kind":"Name","value":"start_date"}},{"kind":"Field","name":{"kind":"Name","value":"end_date"}},{"kind":"Field","name":{"kind":"Name","value":"start_time"}},{"kind":"Field","name":{"kind":"Name","value":"end_time"}},{"kind":"Field","name":{"kind":"Name","value":"priority"}},{"kind":"Field","name":{"kind":"Name","value":"allow"}},{"kind":"Field","name":{"kind":"Name","value":"room"}}]}}]}}]} as unknown as DocumentNode<RulesQuery, RulesQueryVariables>; +export const CreateRuleDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateRule"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"rule"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"InputRule"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createRule"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"rule"},"value":{"kind":"Variable","name":{"kind":"Name","value":"rule"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"en"}},{"kind":"Field","name":{"kind":"Name","value":"sv"}}]}}]}}]} as unknown as DocumentNode<CreateRuleMutation, CreateRuleMutationVariables>; +export const DeleteRuleDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteRule"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteRule"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"en"}},{"kind":"Field","name":{"kind":"Name","value":"sv"}}]}}]}}]} as unknown as DocumentNode<DeleteRuleMutation, DeleteRuleMutationVariables>; diff --git a/frontend/src/index.js b/frontend/src/index.js deleted file mode 100644 index 69f80c6..0000000 --- a/frontend/src/index.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from "react"; -import ReactDOM from "react-dom"; -import App from "./App.jsx"; -import reportWebVitals from "./reportWebVitals"; - -ReactDOM.render( - <React.StrictMode> - <App /> - </React.StrictMode>, - document.getElementById("root"), -); - -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); diff --git a/frontend/src/lib/dates.test.ts b/frontend/src/lib/dates.test.ts new file mode 100644 index 0000000..c6a5f1e --- /dev/null +++ b/frontend/src/lib/dates.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from "vite-plus/test"; +import { localInput, parseDate } from "./dates"; + +describe("booking dates", () => { + it("accepts ISO dates and the legacy milliseconds contract", () => { + const date = new Date("2026-10-25T01:30:00.000Z"); + expect(parseDate(date.toISOString()).getTime()).toBe(date.getTime()); + expect(parseDate(String(date.getTime())).getTime()).toBe(date.getTime()); + }); + it("rejects invalid server values", () => { + expect(() => parseDate("not-a-date")).toThrow("Invalid date"); + }); + it("formats local form values without accidentally converting them to UTC", () => { + const local = new Date(2026, 8, 6, 14, 30); + expect(localInput(local)).toBe("2026-09-06T14:30"); + expect(new Date(localInput(local)).getTime()).toBe(local.getTime()); + }); +}); diff --git a/frontend/src/lib/dates.ts b/frontend/src/lib/dates.ts new file mode 100644 index 0000000..625b568 --- /dev/null +++ b/frontend/src/lib/dates.ts @@ -0,0 +1,9 @@ +import { format, isValid } from "date-fns"; +export function parseDate(value: string): Date { + const date = /^\d+$/.test(value) ? new Date(Number(value)) : new Date(value); + if (!isValid(date)) throw new Error("Invalid date received from the server."); + return date; +} +export function localInput(value: Date) { + return format(value, "yyyy-MM-dd'T'HH:mm"); +} diff --git a/frontend/src/lib/forms.ts b/frontend/src/lib/forms.ts new file mode 100644 index 0000000..74647f8 --- /dev/null +++ b/frontend/src/lib/forms.ts @@ -0,0 +1,4 @@ +export function formText(form: FormData, name: string): string { + const value = form.get(name); + return typeof value === "string" ? value : ""; +} diff --git a/frontend/src/lib/language.tsx b/frontend/src/lib/language.tsx new file mode 100644 index 0000000..53efdb1 --- /dev/null +++ b/frontend/src/lib/language.tsx @@ -0,0 +1,23 @@ +import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from "react"; +type Language = "en" | "sv"; +const LanguageContext = createContext<{ + language: Language; + setLanguage: (language: Language) => void; +}>({ language: "en", setLanguage: () => undefined }); +export function LanguageProvider({ children }: { children: ReactNode }) { + const [language, update] = useState<Language>(() => + localStorage.getItem("bookit-language") === "sv" ? "sv" : "en", + ); + useEffect(() => { + document.documentElement.lang = language; + }, [language]); + const setLanguage = useCallback((value: Language) => { + localStorage.setItem("bookit-language", value); + update(value); + }, []); + return <LanguageContext value={{ language, setLanguage }}>{children}</LanguageContext>; +} +export function useLanguage() { + const context = useContext(LanguageContext); + return { ...context, t: (en: string, sv: string) => (context.language === "sv" ? sv : en) }; +} diff --git a/frontend/src/common/components/event-form/gdpr-agreement.js b/frontend/src/lib/privacy.ts similarity index 95% rename from frontend/src/common/components/event-form/gdpr-agreement.js rename to frontend/src/lib/privacy.ts index 6664765..fd21ff7 100644 --- a/frontend/src/common/components/event-form/gdpr-agreement.js +++ b/frontend/src/lib/privacy.ts @@ -1,5 +1,4 @@ -const GDPRAgreement = { - // eslint-disable-next-line +export const privacyText = { en: "TLDR; Phone number and CID will be stored until \ two weeks after the event ended. Other information about the event, including \ what group booked the event will be stored indefinitely.\n\ @@ -16,7 +15,7 @@ to complain to the Swedish Authority for Privacy Protection (Integritetsskyddsmy if you are dissatisfied with the management of your data. If you have other \ questions about GDPR, please contact the student division board (styrIT) at \ styrit@chalmers.it or the maintainers of this website, digIT, at digit@chalmers.it.", - // eslint-disable-next-line + sv: "TLDR; Ditt telefonnummer och CID kommer sparas tills \ två veckor efter bokningen slutar. Annan data relaterad till bokningen kommer \ sparas tills vidare.\n\ @@ -33,5 +32,3 @@ klaga till Integritetsskyddsmyndigheten om du är missnöjd av hur din data hant Om du har någon fråga om GDPR kan du kontakta styrelsen för IT (styrIT) via \ styrit@chalmers.it, eller de som underhåller denna sida, digIT, via digit@chalmers.it.", }; - -export default GDPRAgreement; diff --git a/frontend/src/lib/rooms.ts b/frontend/src/lib/rooms.ts new file mode 100644 index 0000000..cb9dfdd --- /dev/null +++ b/frontend/src/lib/rooms.ts @@ -0,0 +1,18 @@ +import type { Room } from "@/generated/graphql"; +export const rooms: { id: Room; name: string; color: string }[] = [ + { + id: "BIG_HUB", + name: "Storhubben", + color: "#0C6291", + }, + { + id: "GROUP_ROOM", + name: "Grupprummet", + color: "#E28413", + }, + { id: "CTC", name: "CTC", color: "#A63446" }, + { id: "THE_CLOUD", name: "The Cloud", color: "#3F784C" }, +]; +export function roomName(id: Room) { + return rooms.find((room) => room.id === id)?.name ?? id; +} diff --git a/frontend/src/lib/user.ts b/frontend/src/lib/user.ts new file mode 100644 index 0000000..8e54d17 --- /dev/null +++ b/frontend/src/lib/user.ts @@ -0,0 +1,16 @@ +import { queryOptions, useQuery } from "@tanstack/react-query"; +import { request } from "@/api/client"; +import { CurrentUserDocument } from "@/generated/graphql"; +export const userOptions = queryOptions({ + queryKey: ["user"], + queryFn: async () => { + const { user } = await request(CurrentUserDocument, {}); + if (!user) throw new Error("Please sign in to book a room."); + return user; + }, + staleTime: 60_000, + retry: false, +}); +export function useUser() { + return useQuery(userOptions); +} diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts new file mode 100644 index 0000000..109c4e2 --- /dev/null +++ b/frontend/src/lib/utils.ts @@ -0,0 +1,5 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..93e974e --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,21 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { RouterProvider } from "@tanstack/react-router"; +import { Toaster } from "sonner"; +import { router } from "./router"; +import { LanguageProvider } from "./lib/language"; +import "./styles.css"; +const client = new QueryClient({ defaultOptions: { queries: { retry: 1, staleTime: 20_000 } } }); +const root = document.getElementById("root"); +if (!root) throw new Error("Application root is missing"); +createRoot(root).render( + <StrictMode> + <QueryClientProvider client={client}> + <LanguageProvider> + <RouterProvider router={router} /> + <Toaster richColors position="bottom-right" /> + </LanguageProvider> + </QueryClientProvider> + </StrictMode>, +); diff --git a/frontend/src/pages/booking.tsx b/frontend/src/pages/booking.tsx new file mode 100644 index 0000000..1674e5c --- /dev/null +++ b/frontend/src/pages/booking.tsx @@ -0,0 +1,198 @@ +import { Link, useNavigate, useSearch, useParams } from "@tanstack/react-router"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { useState } from "react"; +import { format } from "date-fns"; +import { ArrowLeft, Pencil, Trash2 } from "lucide-react"; +import { toast } from "sonner"; +import { request, checkMutation } from "@/api/client"; +import { BookingDetailDocument, DeleteBookingDocument, type Room } from "@/generated/graphql"; +import { roomName } from "@/lib/rooms"; +import { parseDate } from "@/lib/dates"; +import { useUser } from "@/lib/user"; +import { useLanguage } from "@/lib/language"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter, +} from "@/components/ui/dialog"; +import { BookingForm } from "@/components/booking-form"; +import { Failure, Loading } from "@/components/feedback"; + +export function NewBookingPage() { + const { t } = useLanguage(); + const { start, end } = useSearch({ from: "/new-event" }); + return ( + <div className="max-w-3xl mx-auto"> + <Link to="/" className="flex gap-2 items-center text-sm text-muted-foreground mb-6"> + <ArrowLeft className="size-4" /> + {t("Back to calendar", "Tillbaka till kalendern")} + </Link> + <h1 className="text-2xl font-semibold mb-6">{t("New booking", "Ny bokning")}</h1> + <Card> + <CardContent> + <BookingForm initialStart={start} initialEnd={end} /> + </CardContent> + </Card> + </div> + ); +} +export function BookingPage() { + const { id } = useParams({ from: "/bookings/$id" }); + const { edit } = useSearch({ from: "/bookings/$id" }); + return <BookingDetails id={id} initialEditing={edit} />; +} +export function BookingDetails({ + id, + initialEditing = false, + onClose, +}: { + id: string; + initialEditing?: boolean; + onClose?: () => void; +}) { + const { t, language } = useLanguage(); + const { data: user } = useUser(); + const [editing, setEditing] = useState(initialEditing); + const [deleting, setDeleting] = useState(false); + const navigate = useNavigate(); + const cache = useQueryClient(); + const query = useQuery({ + queryKey: ["booking", id], + queryFn: () => request(BookingDetailDocument, { id }), + }); + const mutation = useMutation({ + mutationFn: async () => { + const result = await request(DeleteBookingDocument, { id }); + checkMutation(result.deleteEvent, language); + }, + onSuccess: async () => { + await cache.invalidateQueries({ queryKey: ["calendar"] }); + toast.success(t("Booking deleted", "Bokningen raderades")); + onClose?.(); + await navigate({ to: "/" }); + }, + onError: (error) => toast.error(error.message), + }); + if (query.isPending) return <Loading />; + if (query.error) return <Failure error={query.error} />; + const booking = query.data.event; + if (!booking) return <p role="alert">{t("Booking not found.", "Bokningen hittades inte.")}</p>; + const canEdit = user?.is_admin || user?.groups?.includes(booking.booked_as); + return ( + <div className="max-w-3xl mx-auto"> + {!onClose && ( + <Link to="/" className="flex gap-2 items-center text-sm text-muted-foreground mb-6"> + <ArrowLeft className="size-4" /> + {t("Back to calendar", "Tillbaka till kalendern")} + </Link> + )} + <div className="flex flex-wrap justify-between items-center gap-4 mb-6"> + <div> + <h2 className="text-2xl font-semibold">{booking.title}</h2> + </div> + {canEdit && ( + <div className="flex gap-2"> + <Button + variant="outline" + onClick={() => { + if (onClose) { + onClose(); + void navigate({ to: "/edit-event", search: { id } }); + } else setEditing(!editing); + }} + > + <Pencil className="size-4" /> + {t("Edit", "Redigera")} + </Button> + <Button variant="outline" onClick={() => setDeleting(true)}> + <Trash2 className="size-4" /> + {t("Delete", "Radera")} + </Button> + </div> + )} + </div> + <Card> + <CardContent> + {editing && canEdit ? ( + <BookingForm booking={booking} /> + ) : ( + <dl className="grid gap-6 sm:grid-cols-2"> + <div> + <dt className="text-sm text-muted-foreground">{t("Rooms", "Rum")}</dt> + <dd className="mt-2 font-medium"> + {booking.room + .filter((room): room is Room => Boolean(room)) + .map(roomName) + .join(", ")} + </dd> + </div> + <div> + <dt className="text-sm text-muted-foreground">{t("Booking as", "Bokas som")}</dt> + <dd className="mt-2 font-medium"> + <a + className="text-primary underline" + href={`https://gamma.chalmers.it/super-groups/${encodeURIComponent(booking.booked_as)}`} + > + {booking.booked_as} + </a> + </dd> + </div> + <div> + <dt className="text-sm text-muted-foreground">{t("Begins at", "Börjar")}</dt> + <dd className="mt-2">{format(parseDate(booking.start), "yyyy-MM-dd HH:mm")}</dd> + </div> + <div> + <dt className="text-sm text-muted-foreground">{t("Ends at", "Slutar")}</dt> + <dd className="mt-2">{format(parseDate(booking.end), "yyyy-MM-dd HH:mm")}</dd> + </div> + <div className="sm:col-span-2"> + <dt className="text-sm text-muted-foreground">{t("Description", "Beskrivning")}</dt> + <dd className="whitespace-pre-wrap mt-2">{booking.description || "—"}</dd> + </div> + <div> + <dt className="text-sm text-muted-foreground">{t("Booked by", "Bokad av")}</dt> + <dd className="mt-2"> + {booking.booked_by ? ( + <a + className="text-primary underline" + href={`https://gamma.chalmers.it/users/${encodeURIComponent(booking.booked_by)}`} + > + {booking.booked_by} + </a> + ) : ( + "—" + )} + </dd> + </div> + </dl> + )} + </CardContent> + </Card> + <Dialog open={deleting} onOpenChange={setDeleting}> + <DialogContent> + <DialogHeader> + <DialogTitle>{t("Delete booking?", "Radera bokningen?")}</DialogTitle> + <DialogDescription>{booking.title}</DialogDescription> + </DialogHeader> + <DialogFooter> + <Button variant="outline" onClick={() => setDeleting(false)}> + {t("Cancel", "Avbryt")} + </Button> + <Button + variant="destructive" + disabled={mutation.isPending} + onClick={() => mutation.mutate()} + > + {t("Confirm deletion", "Bekräfta radering")} + </Button> + </DialogFooter> + </DialogContent> + </Dialog> + </div> + ); +} diff --git a/frontend/src/pages/calendar.tsx b/frontend/src/pages/calendar.tsx new file mode 100644 index 0000000..039d605 --- /dev/null +++ b/frontend/src/pages/calendar.tsx @@ -0,0 +1,226 @@ +import { useEffect, useState } from "react"; +import { EventCalendar } from "@/components/event-calendar"; +import svLocale from "@fullcalendar/react/locales/sv"; +import enLocale from "@fullcalendar/react/locales/en-gb"; +import { addDays, startOfDay, startOfWeek } from "date-fns"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { Link, useNavigate } from "@tanstack/react-router"; +import { Plus } from "lucide-react"; +import { toast } from "sonner"; +import { request, checkMutation } from "@/api/client"; +import { + CalendarDocument, + UpdateBookingDocument, + type BookingFragment, + type Room, +} from "@/generated/graphql"; +import { rooms } from "@/lib/rooms"; +import { parseDate, localInput } from "@/lib/dates"; +import { useLanguage } from "@/lib/language"; +import { useUser } from "@/lib/user"; +import { Button } from "@/components/ui/button"; +import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; +import { BookingDetails } from "@/pages/booking"; +import { Failure } from "@/components/feedback"; + +export function CalendarPage() { + const { t, language } = useLanguage(); + const { data: user } = useUser(); + const navigate = useNavigate(); + const cache = useQueryClient(); + const [detailId, setDetailId] = useState<string | null>(null); + const [viewport, setViewport] = useState(() => ({ + width: window.innerWidth, + height: window.innerHeight, + })); + useEffect(() => { + const resize = () => setViewport({ width: window.innerWidth, height: window.innerHeight }); + window.addEventListener("resize", resize); + return () => window.removeEventListener("resize", resize); + }, []); + const [range, setRange] = useState(() => ({ + from: startOfWeek(new Date(), { weekStartsOn: 1 }).toISOString(), + to: addDays(startOfWeek(new Date(), { weekStartsOn: 1 }), 7).toISOString(), + })); + const [selectedRooms, setSelectedRooms] = useState<Room[]>(rooms.map((room) => room.id)); + const query = useQuery({ + queryKey: ["calendar", range], + queryFn: () => request(CalendarDocument, range), + }); + const bookings = (query.data?.eventsFT ?? []).filter((event): event is BookingFragment => + Boolean(event), + ); + const visible = bookings.filter((event) => + event.room.some((room) => room && selectedRooms.includes(room)), + ); + const move = useMutation({ + mutationFn: async ({ + booking, + start, + end, + }: { + booking: BookingFragment; + start: Date; + end: Date; + }) => { + const { editEvent } = await request(UpdateBookingDocument, { + event: { + id: booking.id, + title: booking.title, + description: booking.description, + start: start.toISOString(), + end: end.toISOString(), + room: booking.room.filter((room): room is Room => Boolean(room)), + booked_as: booking.booked_as, + booking_terms: true, + }, + }); + checkMutation(editEvent, language); + }, + onSuccess: async () => { + await cache.invalidateQueries({ queryKey: ["calendar"] }); + toast.success(t("Booking moved", "Bokningen flyttades")); + }, + }); + return ( + <> + <h1 className="sr-only">{t("Calendar", "Kalender")}</h1> + <div className="flex flex-wrap justify-between items-center gap-3 mb-4"> + <div className="flex flex-wrap gap-2" aria-label={t("Room filters", "Rumsfilter")}> + {rooms.map((room) => { + const active = selectedRooms.includes(room.id); + return ( + <Button + key={room.id} + size="sm" + aria-pressed={active} + style={{ backgroundColor: active ? room.color : "#737373", color: "#fff" }} + onClick={() => + setSelectedRooms( + active + ? selectedRooms.filter((id) => id !== room.id) + : [...selectedRooms, room.id], + ) + } + > + {room.name} + </Button> + ); + })} + </div> + <Button asChild> + <Link to="/new-event" search={{}}> + <Plus className="size-4" /> + {t("New booking", "Ny bokning")} + </Link> + </Button> + </div> + {query.error && <Failure error={query.error} retry={() => void query.refetch()} />} + <EventCalendar + availableViews={["dayGridMonth", "listWeek", "timeGridWeek"]} + locales={[enLocale, svLocale]} + locale={language === "en" ? "en-gb" : "sv"} + firstDay={1} + initialView={viewport.width > 600 ? "timeGridWeek" : "timeGridDay"} + allDaySlot + weekNumbers + height={viewport.width > 600 ? Math.max(420, viewport.height - 200) : "auto"} + scrollTime="17:00:00" + scrollTimeReset={false} + selectable + eventOverlap + eventTimeFormat={{ hour: "2-digit", minute: "2-digit", hour12: false }} + slotHeaderFormat={{ hour: "2-digit", minute: "2-digit", hour12: false }} + dayHeaderFormat={{ weekday: "short", day: "2-digit", month: "2-digit" }} + views={{ dayGridMonth: { dayHeaderFormat: { weekday: "short" } } }} + datesSet={(info) => + setRange((current) => + current.from === info.start.toISOString() && current.to === info.end.toISOString() + ? current + : { from: info.start.toISOString(), to: info.end.toISOString() }, + ) + } + select={(info) => + void navigate({ + to: "/new-event", + search: { start: localInput(info.start), end: localInput(info.end) }, + }) + } + eventClick={(info) => { + if (info.event.id) setDetailId(info.event.id); + }} + eventDrop={(info) => { + const booking = bookings.find((event) => event.id === info.event.id); + if (!booking || !info.event.start || !info.event.end) { + info.revert(); + return; + } + move.mutate( + { booking, start: info.event.start, end: info.event.end }, + { + onError: (error) => { + info.revert(); + toast.error(error.message); + }, + }, + ); + }} + + eventDidMount={(info) => { + const booking = bookings.find((event) => event.id === info.event.id); + const colors = rooms + .filter((room) => booking?.room.includes(room.id)) + .map((room) => room.color); + if (colors.length > 1) + info.el.style.backgroundImage = `repeating-linear-gradient(45deg, ${colors.map((color, i) => `${color} ${i * 25}px ${(i + 1) * 25}px`).join(", ")})`; + }} + events={[ + ...visible.map((event) => ({ + id: event.id ?? undefined, + title: event.title, + start: parseDate(event.start), + end: parseDate(event.end), + color: rooms.find((room) => event.room.includes(room.id))?.color, + contrastColor: "#fff", + editable: Boolean(user?.is_admin || user?.groups?.includes(event.booked_as)), + durationEditable: false, + })), + ...(query.data?.illegalSlots ?? []) + .filter((slot) => slot.room.some((room) => selectedRooms.includes(room))) + .map((slot) => ({ + start: parseDate(slot.start), + end: parseDate(slot.end), + display: "background", + color: "#EF9A9A", + title: slot.title + (slot.description ? ` — ${slot.description}` : ""), + })), + ...(new Date(range.to) > startOfDay(addDays(new Date(), 63)) + ? [ + { + start: startOfDay(addDays(new Date(), 63)), + end: range.to, + display: "background", + color: "#AAAAAA", + title: t("Outside of booking range", "Utanför bokningsperiod"), + }, + ] + : []), + ]} + /> + <Dialog + open={detailId !== null} + onOpenChange={(open) => { + if (!open) setDetailId(null); + }} + > + <DialogContent + className="sm:max-w-3xl max-h-[90vh] overflow-y-auto" + aria-describedby={undefined} + > + <DialogTitle className="sr-only">{t("Booking details", "Bokningsdetaljer")}</DialogTitle> + {detailId && <BookingDetails id={detailId} onClose={() => setDetailId(null)} />} + </DialogContent> + </Dialog> + </> + ); +} diff --git a/frontend/src/pages/rules.tsx b/frontend/src/pages/rules.tsx new file mode 100644 index 0000000..c50f179 --- /dev/null +++ b/frontend/src/pages/rules.tsx @@ -0,0 +1,480 @@ +import { formText } from "@/lib/forms"; +import { useState, type FormEvent } from "react"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { format } from "date-fns"; +import { Plus, Trash2, ArrowUpDown } from "lucide-react"; +import { toast } from "sonner"; +import { request, checkMutation } from "@/api/client"; +import { + RulesDocument, + CreateRuleDocument, + DeleteRuleDocument, + type Room, + type RulesQuery, +} from "@/generated/graphql"; +import { rooms, roomName } from "@/lib/rooms"; +import { parseDate } from "@/lib/dates"; +import { useUser } from "@/lib/user"; +import { useLanguage } from "@/lib/language"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Badge } from "@/components/ui/badge"; +import { Card } from "@/components/ui/card"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter, +} from "@/components/ui/dialog"; +import { Failure, Loading } from "@/components/feedback"; + +type RuleDetails = NonNullable<NonNullable<RulesQuery["rules"]>[number]>; +type SortKey = + | "title" + | "priority" + | "start_date" + | "end_date" + | "start_time" + | "room" + | "day_mask" + | "allow"; + +function sortValue(rule: RuleDetails, key: SortKey): string | number { + if (key === "priority" || key === "day_mask") return rule[key] ?? 0; + if (key === "allow") return Number(rule.allow); + if (key === "room") return rule.room?.map(roomName).sort().join(", ") ?? ""; + return rule[key] ?? ""; +} + +export function RulesPage() { + const { t, language } = useLanguage(); + const { data: user } = useUser(); + const cache = useQueryClient(); + const [creating, setCreating] = useState(false); + const [deleting, setDeleting] = useState<{ id: string; title: string } | null>(null); + const [selected, setSelected] = useState<Room[]>([]); + const [days, setDays] = useState(0); + const [detailsId, setDetailsId] = useState<string | null>(null); + const [sortKey, setSortKey] = useState<SortKey>("title"); + const [ascending, setAscending] = useState(true); + const [pageIndex, setPageIndex] = useState(0); + const [error, setError] = useState(""); + const weekdays = + language === "en" + ? ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] + : ["Mån", "Tis", "Ons", "Tor", "Fre", "Lör", "Sön"]; + const query = useQuery({ queryKey: ["rules"], queryFn: () => request(RulesDocument, {}) }); + const sorted = (query.data?.rules ?? []) + .filter((rule): rule is RuleDetails => rule !== null) + .sort((a, b) => { + const left = sortValue(a, sortKey); + const right = sortValue(b, sortKey); + const result = + typeof left === "number" && typeof right === "number" + ? left - right + : String(left).localeCompare(String(right), language, { sensitivity: "base" }); + return ascending ? result : -result; + }); + const pageCount = Math.max(1, Math.ceil(sorted.length / 10)); + const currentPage = Math.min(pageIndex, pageCount - 1); + const pageRules = sorted.slice(currentPage * 10, currentPage * 10 + 10); + const details = sorted.find((rule) => rule.id === detailsId); + const columns: { key: SortKey; label: string }[] = [ + { key: "title", label: t("Title", "Titel") }, + { key: "priority", label: t("Priority", "Prioritet") }, + { key: "start_date", label: t("Start date", "Startdatum") }, + { key: "end_date", label: t("End date", "Slutdatum") }, + { key: "start_time", label: t("Time", "Tid") }, + { key: "room", label: t("Rooms", "Rum") }, + { key: "day_mask", label: t("Weekdays", "Veckodagar") }, + { key: "allow", label: t("Availability", "Tillgänglighet") }, + ]; + const dateText = (value: string | null | undefined, withTime = false) => + value ? format(parseDate(value), withTime ? "d MMM yyyy, HH:mm" : "d MMM yyyy") : "—"; + const dayText = (mask: number | null | undefined) => + weekdays.filter((_, index) => ((mask ?? 0) & (1 << index)) !== 0).join(" · ") || "—"; + async function invalidate() { + await cache.invalidateQueries({ queryKey: ["rules"] }); + await cache.invalidateQueries({ queryKey: ["calendar"] }); + } + const create = useMutation({ + mutationFn: async (form: FormData) => { + if (!selected.length || !days) + throw new Error( + t("Select rooms and at least one weekday.", "Välj rum och minst en veckodag."), + ); + const rule = { + title: formText(form, "title").trim(), + description: formText(form, "description"), + priority: Number(form.get("priority")), + allow: form.get("allow") === "true", + day_mask: days, + room: selected, + start_date: formText(form, "start_date"), + end_date: formText(form, "end_date"), + start_time: formText(form, "start_time"), + end_time: formText(form, "end_time"), + }; + if (rule.end_date < rule.start_date || rule.end_time <= rule.start_time) + throw new Error( + t("The rule must end after it starts.", "Regeln måste sluta efter att den börjar."), + ); + const result = await request(CreateRuleDocument, { rule }); + checkMutation(result.createRule, language); + }, + onSuccess: async () => { + await invalidate(); + setCreating(false); + setSelected([]); + toast.success(t("Rule created", "Regeln skapades")); + }, + onError: (reason) => setError(reason.message), + }); + const remove = useMutation({ + mutationFn: async (id: string) => { + const result = await request(DeleteRuleDocument, { id }); + checkMutation(result.deleteRule, language); + }, + onSuccess: async () => { + await invalidate(); + setDeleting(null); + toast.success(t("Rule deleted", "Regeln raderades")); + }, + onError: (reason) => toast.error(reason.message), + }); + function submit(event: FormEvent<HTMLFormElement>) { + event.preventDefault(); + setError(""); + create.mutate(new FormData(event.currentTarget)); + } + return ( + <> + <div className="flex justify-between items-end flex-wrap gap-5 mb-7"> + <h1 className="page-title">{t("Rules", "Regler")}</h1> + {user?.is_admin && ( + <Button + onClick={() => { + setError(""); + setDays(0); + setSelected([]); + setCreating(true); + }} + > + <Plus className="size-4" /> + {t("New rule", "Ny regel")} + </Button> + )} + </div> + {query.isPending ? ( + <Loading /> + ) : query.error ? ( + <Failure error={query.error} retry={() => void query.refetch()} /> + ) : ( + <Card className="p-0 overflow-hidden"> + <Table> + <TableHeader> + <TableRow> + {columns.map((column) => ( + <TableHead + key={column.key} + aria-sort={ + sortKey === column.key ? (ascending ? "ascending" : "descending") : "none" + } + > + <button + className="inline-flex items-center gap-1 whitespace-nowrap py-2" + onClick={() => { + setAscending(sortKey === column.key ? !ascending : true); + setSortKey(column.key); + setPageIndex(0); + }} + > + {column.label} + <ArrowUpDown className="size-3" aria-hidden="true" /> + </button> + </TableHead> + ))} + <TableHead> + <span className="sr-only">{t("Actions", "Åtgärder")}</span> + </TableHead> + </TableRow> + </TableHeader> + <TableBody> + {pageRules.map((rule) => ( + <TableRow key={rule.id}> + <TableCell className="max-w-72 font-medium">{rule.title}</TableCell> + <TableCell>{rule.priority}</TableCell> + <TableCell className="whitespace-nowrap">{dateText(rule.start_date)}</TableCell> + <TableCell className="whitespace-nowrap">{dateText(rule.end_date)}</TableCell> + <TableCell className="whitespace-nowrap"> + {rule.start_time}–{rule.end_time} + </TableCell> + <TableCell> + {rule.room + ?.filter((room): room is Room => Boolean(room)) + .map(roomName) + .join(", ")} + </TableCell> + <TableCell>{dayText(rule.day_mask)}</TableCell> + <TableCell> + <Badge variant={rule.allow ? "secondary" : "destructive"}> + {rule.allow ? t("Allowed", "Tillåten") : t("Blocked", "Spärrad")} + </Badge> + </TableCell> + <TableCell> + <div className="flex items-center gap-1"> + <Button + variant="ghost" + size="sm" + onClick={() => rule.id && setDetailsId(rule.id)} + aria-label={`${t("Details", "Detaljer")} ${rule.title ?? ""}`} + > + {t("Details", "Detaljer")} + </Button> + {user?.is_admin && ( + <Button + size="icon" + variant="ghost" + aria-label={`${t("Delete", "Radera")} ${rule.title ?? ""}`} + onClick={() => + rule.id && setDeleting({ id: rule.id, title: rule.title ?? "" }) + } + > + <Trash2 className="size-4" /> + </Button> + )} + </div> + </TableCell> + </TableRow> + ))} + </TableBody> + </Table> + {!sorted.length && ( + <p className="p-10 text-center text-muted-foreground"> + {t("No booking rules yet.", "Inga bokningsregler ännu.")} + </p> + )} + <div className="flex items-center justify-end gap-3 border-t p-3 text-sm"> + <span> + {t("Page", "Sida")} {currentPage + 1} / {pageCount} + </span> + <Button + variant="outline" + size="sm" + disabled={currentPage === 0} + onClick={() => setPageIndex(currentPage - 1)} + > + {t("Previous", "Föregående")} + </Button> + <Button + variant="outline" + size="sm" + disabled={currentPage + 1 === pageCount} + onClick={() => setPageIndex(currentPage + 1)} + > + {t("Next", "Nästa")} + </Button> + </div> + </Card> + )} + <Dialog + open={Boolean(details)} + onOpenChange={(open) => { + if (!open) setDetailsId(null); + }} + > + <DialogContent className="sm:max-w-2xl max-h-[90vh] overflow-y-auto"> + <DialogHeader> + <DialogTitle>{t("Rule details", "Regeldetaljer")}</DialogTitle> + <DialogDescription>{details?.title}</DialogDescription> + </DialogHeader> + {details && ( + <dl className="grid gap-4 sm:grid-cols-2 text-sm"> + {[ + [t("Title", "Titel"), details.title], + [t("Priority", "Prioritet"), details.priority], + [t("Start date", "Startdatum"), dateText(details.start_date)], + [t("End date", "Slutdatum"), dateText(details.end_date)], + [t("Start time", "Starttid"), details.start_time], + [t("End time", "Sluttid"), details.end_time], + [t("Description", "Beskrivning"), details.description], + [t("Rooms", "Rum"), details.room?.map(roomName).join(", ")], + [t("Weekdays", "Veckodagar"), dayText(details.day_mask)], + [ + t("Availability", "Tillgänglighet"), + details.allow ? t("Allowed", "Tillåten") : t("Blocked", "Spärrad"), + ], + [t("Created", "Skapad"), dateText(details.created_at, true)], + [t("Updated", "Uppdaterad"), dateText(details.updated_at, true)], + ].map(([label, value]) => ( + <div key={label}> + <dt className="text-muted-foreground">{label}</dt> + <dd className="mt-1 whitespace-pre-wrap">{value ?? "—"}</dd> + </div> + ))} + </dl> + )} + <DialogFooter> + <Button variant="outline" onClick={() => setDetailsId(null)}> + {t("Close", "Stäng")} + </Button> + </DialogFooter> + </DialogContent> + </Dialog> + <Dialog open={creating} onOpenChange={setCreating}> + <DialogContent className="sm:max-w-2xl max-h-[90vh] overflow-y-auto"> + <DialogHeader> + <DialogTitle>{t("New rule", "Ny regel")}</DialogTitle> + <DialogDescription className="sr-only">{t("New rule", "Ny regel")}</DialogDescription> + </DialogHeader> + <form onSubmit={submit} className="space-y-5"> + <div className="field"> + <Label htmlFor="rule-title">{t("Title", "Titel")}</Label> + <Input id="rule-title" name="title" required maxLength={200} /> + </div> + <div className="grid grid-cols-2 gap-4"> + <div className="field"> + <Label htmlFor="priority">{t("Priority", "Prioritet")}</Label> + <Input id="priority" name="priority" type="number" required defaultValue={10} /> + </div> + <div className="field"> + <Label htmlFor="allow">{t("Availability", "Tillgänglighet")}</Label> + <select id="allow" name="allow" className="native-select"> + <option value="true">{t("Allowed", "Tillåten")}</option> + <option value="false">{t("Blocked", "Spärrad")}</option> + </select> + </div> + <div className="field"> + <Label htmlFor="start_date">{t("Start date", "Startdatum")}</Label> + <Input + id="start_date" + name="start_date" + type="date" + required + defaultValue={format(new Date(), "yyyy-MM-dd")} + /> + </div> + <div className="field"> + <Label htmlFor="end_date">{t("End date", "Slutdatum")}</Label> + <Input + id="end_date" + name="end_date" + type="date" + required + defaultValue="2040-12-31" + /> + </div> + <div className="field"> + <Label htmlFor="start_time">{t("Start time", "Starttid")}</Label> + <Input + id="start_time" + name="start_time" + type="time" + required + defaultValue="08:00" + /> + </div> + <div className="field"> + <Label htmlFor="end_time">{t("End time", "Sluttid")}</Label> + <Input id="end_time" name="end_time" type="time" required defaultValue="17:00" /> + </div> + </div> + <fieldset> + <legend className="text-sm font-medium mb-3">{t("Weekdays", "Veckodagar")}</legend> + <div className="flex flex-wrap gap-3"> + {weekdays.map((day, index) => ( + <label className="flex gap-2 items-center text-sm" key={day}> + <Checkbox + aria-label={day} + checked={Boolean(days & (1 << index))} + onCheckedChange={(checked) => + setDays(checked ? days | (1 << index) : days & ~(1 << index)) + } + /> + {day} + </label> + ))} + </div> + </fieldset> + <fieldset> + <legend className="text-sm font-medium mb-3">{t("Rooms", "Rum")}</legend> + <div className="grid grid-cols-2 gap-3"> + {rooms.map((room) => ( + <label className="flex gap-2 items-center text-sm" key={room.id}> + <Checkbox + aria-label={room.name} + checked={selected.includes(room.id)} + onCheckedChange={(checked) => + setSelected( + checked + ? [...selected, room.id] + : selected.filter((id) => id !== room.id), + ) + } + /> + {room.name} + </label> + ))} + </div> + </fieldset> + <div className="field"> + <Label htmlFor="rule-description">{t("Description", "Beskrivning")}</Label> + <Textarea id="rule-description" name="description" /> + </div> + {error && ( + <p role="alert" className="text-sm text-destructive"> + {error} + </p> + )} + <DialogFooter> + <Button type="button" variant="outline" onClick={() => setCreating(false)}> + {t("Cancel", "Avbryt")} + </Button> + <Button type="submit" disabled={create.isPending}> + {t("Save rule", "Spara regel")} + </Button> + </DialogFooter> + </form> + </DialogContent> + </Dialog> + <Dialog + open={Boolean(deleting)} + onOpenChange={(open) => { + if (!open) setDeleting(null); + }} + > + <DialogContent> + <DialogHeader> + <DialogTitle>{t("Delete rule?", "Radera regeln?")}</DialogTitle> + <DialogDescription>{deleting?.title}</DialogDescription> + </DialogHeader> + <DialogFooter> + <Button variant="outline" onClick={() => setDeleting(null)}> + {t("Cancel", "Avbryt")} + </Button> + <Button + variant="destructive" + disabled={remove.isPending} + onClick={() => deleting && remove.mutate(deleting.id)} + > + {t("Confirm deletion", "Bekräfta radering")} + </Button> + </DialogFooter> + </DialogContent> + </Dialog> + </> + ); +} diff --git a/frontend/src/reportWebVitals.js b/frontend/src/reportWebVitals.js deleted file mode 100644 index 5253d3a..0000000 --- a/frontend/src/reportWebVitals.js +++ /dev/null @@ -1,13 +0,0 @@ -const reportWebVitals = onPerfEntry => { - if (onPerfEntry && onPerfEntry instanceof Function) { - import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { - getCLS(onPerfEntry); - getFID(onPerfEntry); - getFCP(onPerfEntry); - getLCP(onPerfEntry); - getTTFB(onPerfEntry); - }); - } -}; - -export default reportWebVitals; diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx new file mode 100644 index 0000000..4d35187 --- /dev/null +++ b/frontend/src/router.tsx @@ -0,0 +1,165 @@ +import { + createRootRoute, + createRoute, + createRouter, + lazyRouteComponent, + redirect, + Link, + Outlet, +} from "@tanstack/react-router"; +import { CalendarDays, LogOut, ShieldCheck } from "lucide-react"; +import { useEffect } from "react"; +import { Button } from "@/components/ui/button"; +import { useUser } from "@/lib/user"; +import { useLanguage } from "@/lib/language"; +import { Failure, Loading } from "@/components/feedback"; + +function Layout() { + const user = useUser(); + const { t, language, setLanguage } = useLanguage(); + useEffect(() => { + if (user.data?.locale && !localStorage.getItem("bookit-language")) { + setLanguage(user.data.locale.startsWith("sv") ? "sv" : "en"); + } + }, [user.data?.locale, setLanguage]); + return ( + <> + <a + href="#content" + className="sr-only focus:not-sr-only focus:absolute focus:z-50 focus:bg-white focus:p-3" + > + {t("Skip to content", "Till innehållet")} + </a> + <header className="bg-white border-b"> + <div className="max-w-[1560px] mx-auto px-4 sm:px-12 flex items-center justify-between min-h-20 gap-4"> + <div className="flex items-center gap-2 sm:gap-12"> + <Link to="/" aria-label="BookIT home" className="flex items-center gap-2.5"> + <span className="size-9 rounded-lg bg-[#2196f3] text-white grid place-items-center"> + <CalendarDays className="size-5" /> + </span> + <span className="hidden sm:inline text-2xl tracking-tight font-bold"> + book<span className="text-[#2196f3]">IT</span> + </span> + </Link> + <nav aria-label={t("Main navigation", "Huvudnavigation")} className="flex gap-1"> + <Link + to="/" + activeOptions={{ exact: true }} + activeProps={{ className: "bg-blue-50 text-primary" }} + className="rounded-md px-3 py-2 text-sm font-medium" + > + {t("Calendar", "Kalender")} + </Link> + <Link + to="/rules" + activeProps={{ className: "bg-blue-50 text-primary" }} + className="rounded-md px-3 py-2 text-sm font-medium" + > + {t("Rules", "Regler")} + </Link> + </nav> + </div> + <div className="flex items-center gap-2"> + <Button + variant="ghost" + size="sm" + aria-label={t("Switch to Swedish", "Byt till engelska")} + onClick={() => setLanguage(language === "en" ? "sv" : "en")} + > + {language === "en" ? "SV" : "EN"} + </Button> + <div className="hidden md:flex text-sm items-center gap-2 border-l pl-4 ml-2"> + {user.data?.is_admin && <ShieldCheck className="size-4 text-primary" />} + {user.data?.nickname || user.data?.name || user.data?.cid} + </div> + <Button variant="ghost" size="icon" asChild> + <a href="/api/logout" aria-label={t("Sign out", "Logga ut")}> + <LogOut className="size-4" /> + </a> + </Button> + </div> + </div> + </header> + <main id="content" className="app-shell"> + {user.isPending ? ( + <Loading /> + ) : user.error ? ( + <Failure error={user.error} retry={() => void user.refetch()} /> + ) : ( + <Outlet /> + )} + </main> + </> + ); +} +const rootRoute = createRootRoute({ + component: Layout, + notFoundComponent: () => ( + <div className="text-center p-12"> + <h1 className="page-title">404</h1> + <p className="my-5">Page not found / Sidan hittades inte</p> + <Link to="/">Back to calendar</Link> + </div> + ), + errorComponent: ({ error, reset }) => <Failure error={error} retry={reset} />, +}); +const homeRoute = createRoute({ + getParentRoute: () => rootRoute, + path: "/", + component: lazyRouteComponent(() => import("@/pages/calendar"), "CalendarPage"), +}); +const newRoute = createRoute({ + getParentRoute: () => rootRoute, + path: "/new-event", + validateSearch: (search: Record<string, unknown>): { start?: string; end?: string } => ({ + start: + typeof search.start === "string" && !Number.isNaN(Date.parse(search.start)) + ? search.start + : undefined, + end: + typeof search.end === "string" && !Number.isNaN(Date.parse(search.end)) + ? search.end + : undefined, + }), + component: lazyRouteComponent(() => import("@/pages/booking"), "NewBookingPage"), +}); +const detailRoute = createRoute({ + getParentRoute: () => rootRoute, + path: "/bookings/$id", + validateSearch: (search: Record<string, unknown>): { edit: boolean } => ({ + edit: search.edit === true || search.edit === "true", + }), + component: lazyRouteComponent(() => import("@/pages/booking"), "BookingPage"), +}); +const rulesRoute = createRoute({ + getParentRoute: () => rootRoute, + path: "/rules", + component: lazyRouteComponent(() => import("@/pages/rules"), "RulesPage"), +}); +// Preserve bookmarks from the previous router. +const legacyEditRoute = createRoute({ + getParentRoute: () => rootRoute, + path: "/edit-event", + validateSearch: (search: Record<string, unknown>): { id?: string } => ({ + id: typeof search.id === "string" ? search.id : undefined, + }), + beforeLoad: ({ search }) => { + if (search.id) + throw redirect({ + to: "/bookings/$id", + params: { id: search.id }, + search: { edit: true }, + replace: true, + }); + throw redirect({ to: "/", replace: true }); + }, +}); +export const router = createRouter({ + scrollRestoration: true, + routeTree: rootRoute.addChildren([homeRoute, newRoute, detailRoute, rulesRoute, legacyEditRoute]), +}); +declare module "@tanstack/react-router" { + interface Register { + router: typeof router; + } +} diff --git a/frontend/src/styles.css b/frontend/src/styles.css new file mode 100644 index 0000000..5e7738f --- /dev/null +++ b/frontend/src/styles.css @@ -0,0 +1,102 @@ +@import "tailwindcss"; +@import "tw-animate-css"; + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--foreground); + --color-popover: var(--card); + --color-popover-foreground: var(--foreground); + --color-primary: var(--primary); + --color-primary-foreground: #fff; + --color-secondary: #e8f3fb; + --color-secondary-foreground: #1769aa; + --color-muted: #f1f5f8; + --color-muted-foreground: #617083; + --color-accent: #e8f3fb; + --color-accent-foreground: #1769aa; + --color-destructive: #b42339; + --color-border: #dfe6ed; + --color-input: #d1dae4; + --color-ring: #2196f3; + --color-chart-1: var(--chart-1); + --color-chart-3: var(--chart-3); + --radius-sm: 0.375rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + --radius-xl: 1rem; +} +:root { + --background: #f6f8fb; + --foreground: #17283a; + --card: #fff; + --primary: #1769aa; + --primary-foreground: #fff; + --chart-1: #2196f3; + --chart-3: #ff9100; + font-family: "Inter", "Segoe UI", Arial, sans-serif; + color: var(--foreground); + background: var(--background); +} +* { + border-color: var(--color-border); +} +body { + margin: 0; + min-width: 320px; +} +button, +a, +input, +select, +textarea { + -webkit-tap-highlight-color: transparent; +} +button:not(:disabled), +[role="button"] { + cursor: pointer; +} +a, +button, +input, +select, +textarea { + outline-offset: 4px; +} +.app-shell { + max-width: 1560px; + margin: auto; + padding: 2.5rem 3rem 4rem; +} +.eyebrow { + font-size: 0.7rem; + font-weight: 700; + letter-spacing: 0.16em; + text-transform: uppercase; + color: #697a8d; +} +.page-title { + font-size: clamp(1.8rem, 3vw, 2.6rem); + line-height: 1.2; + letter-spacing: -0.045em; + font-weight: 700; +} +.field { + display: grid; + gap: 0.55rem; +} +.native-select { + min-height: 2.5rem; + width: 100%; + border: 1px solid var(--color-input); + border-radius: 0.5rem; + padding: 0.5rem 0.75rem; + font-size: 0.875rem; + background: white; +} +@media (max-width: 760px) { + .app-shell { + padding: 1.5rem 1rem; + } +} diff --git a/frontend/src/use-cases/auth/index.jsx b/frontend/src/use-cases/auth/index.jsx deleted file mode 100644 index 85118b6..0000000 --- a/frontend/src/use-cases/auth/index.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useContext, useEffect } from "react"; -import { useHistory } from "react-router"; -import { exchangeCode } from "../../api/backend.api"; -import UserContext from "../../common/contexts/user-context"; -import { useDigitTranslations } from "@cthit/react-digit-components"; - -const Callback = () => { - const history = useHistory(); - const [, setUser] = useContext(UserContext); - const [, , setActiveLanguage] = useDigitTranslations({}); - useEffect(() => { - const authenticateUser = async () => { - const params = new URLSearchParams(window.location.search); - const user = await exchangeCode(params.get("code"), params.get("state")); - setActiveLanguage(user.language); - setUser(user); - history.push("/"); - }; - authenticateUser(); - }, [setUser, history, setActiveLanguage]); - return null; -}; - -export default Callback; diff --git a/frontend/src/use-cases/edit-event/edit-event.translations.json b/frontend/src/use-cases/edit-event/edit-event.translations.json deleted file mode 100644 index 6b4d64f..0000000 --- a/frontend/src/use-cases/edit-event/edit-event.translations.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "edit_event": ["Edit event", "Redigera bokning"], - "event_edited": ["The event has been edited", "Bokningen har redigeras"] -} diff --git a/frontend/src/use-cases/edit-event/index.jsx b/frontend/src/use-cases/edit-event/index.jsx deleted file mode 100644 index 64bbbe7..0000000 --- a/frontend/src/use-cases/edit-event/index.jsx +++ /dev/null @@ -1,83 +0,0 @@ -import { useState, useEffect } from "react"; -import { useHistory } from "react-router"; -import { getFullEvent } from "../../api/backend.api"; -import EventForm from "../../common/components/event-form"; -import { - DigitLayout, - DigitDesign, - useDigitToast, - useDigitTranslations, -} from "@cthit/react-digit-components"; -import { formatDT } from "../../utils/utils.js"; -import { editEvent } from "../../api/backend.api"; -import transitions from "./edit-event.translations.json"; - -const formatEvent = event => { - return { - ...event, - start: new Date(Number(event.start)), - end: new Date(Number(event.end)), - booking_terms: true, - gdpr: true, - cubsec: true, - }; -}; - -const EditEvent = () => { - const history = useHistory(); - const [event, setEvent] = useState(null); - const [id, setId] = useState(null); - const [openToast] = useDigitToast({ - duration: 7000, - actionText: "Ok", - actionHandler: () => {}, - }); - const [texts, activeLanguage] = useDigitTranslations(transitions); - - useEffect(() => { - const fetchData = async () => { - const params = new URLSearchParams(window.location.search); - let id = params.get("id"); - setId(id); - setEvent(formatEvent(await getFullEvent(id))); - }; - fetchData(); - }, []); - - const handleSubmit = async event_ => { - const res = await editEvent({ - id: id, - title: event_.title, - phone: event_.phone, - room: event_.room, - start: formatDT(event_.start), - end: formatDT(event_.end), - description: event_.description, - booked_as: event_.booked_as, - booking_terms: event_.booking_terms, - }); - if (res === null) { - openToast({ - text: texts.event_edited, - }); - history.push("/"); - return; - } - openToast({ - text: res[activeLanguage], - }); - }; - - return ( - <DigitLayout.Center> - <DigitDesign.Card> - <DigitDesign.CardBody> - <DigitDesign.CardTitle text={texts.edit_event} /> - <EventForm initialValues={event} onSubmit={handleSubmit} /> - </DigitDesign.CardBody> - </DigitDesign.Card> - </DigitLayout.Center> - ); -}; - -export default EditEvent; diff --git a/frontend/src/use-cases/home/home.translations.json b/frontend/src/use-cases/home/home.translations.json deleted file mode 100644 index 6554760..0000000 --- a/frontend/src/use-cases/home/home.translations.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "event_edited": ["The event has been edited", "Bokningen har redigerats"], - "out_of_range": ["Outside of booking range", "Utanför bokningsperiod"] -} diff --git a/frontend/src/use-cases/home/index.css b/frontend/src/use-cases/home/index.css deleted file mode 100644 index 39b0ca4..0000000 --- a/frontend/src/use-cases/home/index.css +++ /dev/null @@ -1,8 +0,0 @@ -.chip { - border-radius: 1.2rem; - color: white; - padding: 0.7rem; - margin-left: 0.5rem; - margin-right: 0.5rem; - cursor: pointer; -} diff --git a/frontend/src/use-cases/home/index.jsx b/frontend/src/use-cases/home/index.jsx deleted file mode 100644 index 1a1fc09..0000000 --- a/frontend/src/use-cases/home/index.jsx +++ /dev/null @@ -1,194 +0,0 @@ -import { - useDigitCustomDialog, - useDigitToast, - useDigitTranslations, -} from "@cthit/react-digit-components"; -import { useHistory } from "react-router"; -import { editEvent, getEvents } from "../../api/backend.api"; -import AddEventButton from "../../common/elements/add-event-button"; -import ROOMS from "../../common/rooms"; -import Calendar from "./views/calendar.view"; -import DetailedView from "./views/detailed-view.view"; -import "./index.css"; -import useMobileQuery from "../../common/hooks/use-mobile-query"; -import { getIllegalSlots } from "../../api/backend.api"; -import { useContext, useCallback, useState } from "react"; -import UserContext from "../../common/contexts/user-context"; -import { overlap } from "../../utils/utils"; -import translations from "./home.translations.json"; - -const style = document.querySelector("#room-styles"); - -const getClassName = rooms => { - let name = "event"; - for (const i in rooms) { - name += "-" + rooms[i].toLowerCase(); - } - name += rooms.length; - if (!style.innerHTML.includes(name)) { - style.innerHTML += `.${name}{background: repeating-linear-gradient(45deg,`; - let px = 0; - for (const i in rooms) { - style.innerHTML += `var(--bg_${rooms[i].toLowerCase()}) ${px}px ,`; - px += 25; - style.innerHTML += `var(--bg_${rooms[i].toLowerCase()}) ${px}px ,`; - } - style.innerHTML = `${style.innerHTML.slice( - 0, - style.innerHTML.length - 1, - )});}\n`; - } - return name; -}; - -const getColorVariables = () => { - let variables = {}; - for (const i in ROOMS) { - variables["--bg_" + ROOMS[i].value.toLowerCase()] = ROOMS[i].color; - } - return variables; -}; -const colorVariables = getColorVariables(); - -const Home = () => { - const [user] = useContext(UserContext); - const history = useHistory(); - const [openDialog, closeDialog] = useDigitCustomDialog({ - title: "Event", - }); - const isMobile = useMobileQuery(); - const [filters, setFilters] = useState(ROOMS.map(r => r.value)); - const [texts, activeLanguage] = useDigitTranslations(translations); - const [openToast] = useDigitToast({ - duration: 7000, - actionText: "Ok", - actionHandler: () => {}, - }); - - const getCalendarEvents = async info => { - const events = await getEvents(info.start, info.end); - - const illegalSlots = await getIllegalSlots(info.start, info.end); - console.log(events.filter(r => r.title === "asdfa")[0]); - - return [ - ...events - .filter(e => overlap(e.room, filters)) - .map(e => ({ - ...e, - className: getClassName(e.room.sort()), - start: new Date(Number(e.start)), - end: new Date(Number(e.end)), - editable: user.groups.includes(e.booked_as) || user.is_admin, - durationEditable: false, - room: e.room.sort(), - })), - ...illegalSlots.map(e => ({ - backgroundColor: "#EF9A9A", - start: new Date(Number(e.start)), - end: new Date(Number(e.end)), - display: "background", - title: e.title + (e.description ? ` - ` + e.description : ""), - })), - { - backgroundColor: "#AAAAAA", - startRecur: new Date(new Date(Date.now() + 5443200000).toDateString()), - display: "background", - title: texts.out_of_range, - }, - ]; - }; - - const getCalendarEventsCallback = useCallback(getCalendarEvents, [ - texts, - filters, - user, - ]); - const toggleChip = room => { - if (filters.includes(room)) { - setFilters(filters.filter(f => f !== room)); - } else { - setFilters([...filters, room]); - } - }; - - const onEventDrop = ({ event, revert }) => { - editEvent({ - id: event.id, - start: event.start, - end: event.end, - //Required by backend - title: event.title, - room: event._def.extendedProps.room, - phone: event._def.extendedProps.phone, - booked_as: event._def.extendedProps.booked_as, - booking_terms: true, - }).then(err => { - if (err) { - revert(); - openToast({ - text: err[activeLanguage], - }); - } else { - openToast({ - text: texts.event_edited, - }); - } - }); - }; - - return ( - <div - style={{ - width: "100%", - margin: `${isMobile ? "1rem 0" : "1rem"}`, - height: "40rem", - fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif', - ...colorVariables, - }} - > - <div - style={{ - display: "flex", - alignContent: "center", - marginBottom: "0.25rem", - }} - > - {ROOMS.map(r => ( - <div - className="chip" - style={{ - backgroundColor: filters.includes(r.value) ? r.color : "gray", - }} - onClick={() => toggleChip(r.value)} - > - {r.text} - </div> - ))} - </div> - <Calendar - getEvents={getCalendarEventsCallback} - eventClick={value => - openDialog({ - title: value.event._def.title, - renderMain: () => ( - <DetailedView - event_id={value.event._def.publicId} - onClose={closeDialog} - // This should not be needed, but context does not work in the detailed view - user={user} - /> - ), - }) - } - onSelect={value => - history.push("/new-event", { start: value.start, end: value.end }) - } - onEventDrop={onEventDrop} - /> - <AddEventButton /> - </div> - ); -}; - -export default Home; diff --git a/frontend/src/use-cases/home/views/calendar-view.css b/frontend/src/use-cases/home/views/calendar-view.css deleted file mode 100644 index c5b3254..0000000 --- a/frontend/src/use-cases/home/views/calendar-view.css +++ /dev/null @@ -1,14 +0,0 @@ -.fc .fc-toolbar { - flex-wrap: wrap; - margin-left: 0.8rem; -} - -@media (max-width: 380px) { - .fc .fc-toolbar { - margin-left: 0; - } - - .fc .fc-toolbar-title { - margin-left: 0.8rem; - } -} diff --git a/frontend/src/use-cases/home/views/calendar.view.jsx b/frontend/src/use-cases/home/views/calendar.view.jsx deleted file mode 100644 index 7733d9c..0000000 --- a/frontend/src/use-cases/home/views/calendar.view.jsx +++ /dev/null @@ -1,68 +0,0 @@ -import FullCallendar from "@fullcalendar/react"; -import dayGridPlugin from "@fullcalendar/daygrid"; -import timeGridPlugin from "@fullcalendar/timegrid"; -import listPlugin from "@fullcalendar/list"; -import interactionPlugin from "@fullcalendar/interaction"; -import momentPlugin from "@fullcalendar/moment"; -import svLocale from "@fullcalendar/core/locales/sv"; -import enLocale from "@fullcalendar/core/locales/en-gb"; -import { useDigitTranslations } from "@cthit/react-digit-components"; -import "./calendar-view.css"; -import useHeight from "../../../common/hooks/change-calendar-size"; - -const Calendar = ({ getEvents, eventClick, onSelect, onEventDrop, ref }) => { - getEvents = getEvents ?? (() => new Promise(res => res([]))); - eventClick = eventClick ?? (() => {}); - onSelect = onSelect ?? (() => {}); - - const [, activeLanguage] = useDigitTranslations({}); - const calculatedScreenHeight = useHeight().height - 200; - - return ( - <FullCallendar - locales={[enLocale, svLocale]} - locale={activeLanguage} - select={onSelect} - selectable - firstDay={1} - eventClick={eventClick} - eventTimeFormat={{ - hour: "2-digit", - minute: "2-digit", - hour12: false, - }} - slotLabelFormat={{ - hour: "2-digit", - minute: "2-digit", - hour12: false, - }} - dayHeaderFormat={"ddd DD/MM"} - views={{dayGridMonth: {dayHeaderFormat: "ddd"}}} - rerenderDelay={1000} - allDaySlot - weekNumbers - editable - headerToolbar={{ - start: "title", - center: "", - end: "dayGridMonth,listWeek,timeGridWeek,today,prev,next", - }} - plugins={[ - momentPlugin, - interactionPlugin, - dayGridPlugin, - timeGridPlugin, - listPlugin, - ]} - initialView={window.innerWidth > 600 ? "timeGridWeek" : "timeGridDay"} - eventOverlap - height={window.innerWidth > 600 ? calculatedScreenHeight + "px" : "auto"} - scrollTime={"17:00:00"} - scrollTimeReset={false} - events={getEvents} - eventDrop={onEventDrop} - /> - ); -}; - -export default Calendar; diff --git a/frontend/src/use-cases/home/views/detailed-view.css b/frontend/src/use-cases/home/views/detailed-view.css deleted file mode 100644 index 3f435b7..0000000 --- a/frontend/src/use-cases/home/views/detailed-view.css +++ /dev/null @@ -1,4 +0,0 @@ -.container { - display: flex; - justify-content: space-between; -} diff --git a/frontend/src/use-cases/home/views/detailed-view.translations.json b/frontend/src/use-cases/home/views/detailed-view.translations.json deleted file mode 100644 index c0bc96b..0000000 --- a/frontend/src/use-cases/home/views/detailed-view.translations.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "bookedBy": ["Booked by:", "Bokad av:"], - "description": ["Description:", "Beskrivning:"], - "start": ["Start:", "Börjar:"], - "end": ["End:", "Slutar:"], - "rooms": ["Rooms:", "Rum:"], - "event_deleted": ["The event was deleted", "Bokningen raderades"] -} diff --git a/frontend/src/use-cases/home/views/detailed-view.view.jsx b/frontend/src/use-cases/home/views/detailed-view.view.jsx deleted file mode 100644 index 37349cb..0000000 --- a/frontend/src/use-cases/home/views/detailed-view.view.jsx +++ /dev/null @@ -1,103 +0,0 @@ -import { - DigitDisplayData, - DigitText, - DigitButton, - useDigitTranslations, - useDigitToast, -} from "@cthit/react-digit-components"; -import { useEffect, useState } from "react"; -import { useHistory } from "react-router-dom"; -import { deleteEvent, getEvent } from "../../../api/backend.api"; -import ROOMS from "../../../common/rooms"; -import translations from "./detailed-view.translations.json"; -import "./detailed-view.css"; -import { formatDT } from "../../../utils/utils"; - -const DetailedView = ({ event_id, onClose, onDelete, user }) => { - const history = useHistory(); - const [event, setEvent] = useState({}); - const [texts, activeLanguage] = useDigitTranslations(translations); - const [openToast] = useDigitToast({ - duration: 3000, - actionText: "Ok", - actionHandler: () => {}, - }); - - useEffect(() => { - getEvent(event_id) - .then(res => - setEvent({ - ...res, - start: formatDT(Number(res.start)), - end: formatDT(Number(res.end)), - _booked_by: ( - <> - <a href={"https://gamma.chalmers.it/users/" + res.booked_by}> - {res.booked_by} - </a> - {" via "} - <a - href={"https://gamma.chalmers.it/super-groups/" + res.booked_as} - > - {res.booked_as} - </a> - </> - ), - room: res.room - .sort() - .map(r => ( - <DigitText.Text text={ROOMS.find(e => e.value === r).text} /> - )), - }), - ) - .catch(() => {}); - }, [event_id]); - return ( - <> - <DigitDisplayData - data={event} - keysText={{ - _booked_by: texts.bookedBy, - description: texts.description, - start: texts.start, - end: texts.end, - room: texts.rooms, - }} - keysOrder={["_booked_by", "description", "start", "end", "room"]} - /> - {user.groups.includes(event.booked_as) || user.is_admin ? ( - <div className="container"> - <DigitButton - text="Edit" - outlined - onClick={() => { - onClose(); - history.push(`/edit-event?id=${event_id}`); - }} - /> - <DigitButton - text="Delete" - outlined - onClick={() => { - deleteEvent(event_id).then(res => { - onClose(); - if (res) { - openToast({ - text: res[activeLanguage], - }); - } else { - openToast({ - text: texts.event_deleted, - }); - window.location.href = "/"; - } - }); - }} - /> - </div> - ) : null} - </> - ); -}; - -export default DetailedView; diff --git a/frontend/src/use-cases/new-event/index.jsx b/frontend/src/use-cases/new-event/index.jsx deleted file mode 100644 index 64a434a..0000000 --- a/frontend/src/use-cases/new-event/index.jsx +++ /dev/null @@ -1,75 +0,0 @@ -import { - DigitDesign, - DigitLayout, - useDigitToast, - useDigitTranslations, -} from "@cthit/react-digit-components"; -import { createEvent } from "../../api/backend.api"; -import { useHistory } from "react-router"; -import { formatDT } from "../../utils/utils"; -import transitions from "./new-event.translations.json"; -import EventForm from "../../common/components/event-form"; -import moment from "moment"; - -const NewReservation = ({ - history: { - location: { state }, - }, -}) => { - const [openToast] = useDigitToast({ - duration: 7000, - actionText: "Ok", - actionHandler: () => {}, - }); - const history = useHistory(); - const [texts, activeLanguage] = useDigitTranslations(transitions); - - const handleSubmit = async event => { - const res = await createEvent({ - title: event.title, - phone: event.phone, - room: event.room, - start: formatDT(event.start), - end: formatDT(event.end), - description: event.description, - booked_as: event.booked_as, - booking_terms: event.booking_terms, - }); - if (res === null) { - openToast({ - text: texts.new_event_created, - }); - history.push("/"); - return; - } - openToast({ - text: res[activeLanguage], - }); - }; - - const default_begin_date = new Date(); - const default_end_date = moment(new Date()).add(1, "h"); - - const initialValues = { - title: "", - phone: "", - room: ["BIG_HUB"], - start: state ? state.start : default_begin_date, - end: state ? state.end : default_end_date, - description: "", - booked_as: "", - }; - - return ( - <DigitLayout.Center> - <DigitDesign.Card> - <DigitDesign.CardBody> - <DigitDesign.CardTitle text={texts.new_booking} /> - <EventForm initialValues={initialValues} onSubmit={handleSubmit} /> - </DigitDesign.CardBody> - </DigitDesign.Card> - </DigitLayout.Center> - ); -}; - -export default NewReservation; diff --git a/frontend/src/use-cases/new-event/new-event.translations.json b/frontend/src/use-cases/new-event/new-event.translations.json deleted file mode 100644 index ab8d05a..0000000 --- a/frontend/src/use-cases/new-event/new-event.translations.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "new_booking": ["New Booking", "Ny bokning"], - "new_event_created": ["A new event was created", "Ett nytt event skapades"] -} diff --git a/frontend/src/use-cases/rules/day-mask.element.jsx b/frontend/src/use-cases/rules/day-mask.element.jsx deleted file mode 100644 index f16f5fb..0000000 --- a/frontend/src/use-cases/rules/day-mask.element.jsx +++ /dev/null @@ -1,61 +0,0 @@ -import { DigitCheckbox, DigitText } from "@cthit/react-digit-components"; -import { useCallback } from "react"; -import "./day-mask.style.css"; - -const dayArray = day_mask => { - const days = []; - for (let i = 0; i < 7; i++) { - days.push({ value: day_mask % 2, id: i }); - day_mask >>= 1; - } - return days; -}; - -const DayMask = ({ day_mask }) => { - const daysArr = useCallback(dayArray, [day_mask]); - return ( - <div className="mask-container"> - {daysArr(day_mask).map(d => ( - <div - key={d.id} - className="day" - style={{ background: d.value ? "#00B74A" : "#B71C1C " }} - ></div> - ))} - </div> - ); -}; - -export const DayMaskInput = ({ value, onChange, label }) => { - const daysArr = useCallback(dayArray, [value]); - - return ( - <div> - <div className="mask-container" style={{ paddingLeft: "1.1rem" }}> - {label.split(" ").map(letter => ( - <DigitText.Title className="day-input" text={letter} /> - ))} - </div> - <div className="mask-container"> - {daysArr(value).map((d, index) => ( - <div className="day-input" key={d.id}> - <DigitCheckbox - primary - value={d.value} - onChange={event => { - const day = 1 << index; - if (event.target.checked) { - onChange(value + day); - } else { - onChange(value - day); - } - }} - /> - </div> - ))} - </div> - </div> - ); -}; - -export default DayMask; diff --git a/frontend/src/use-cases/rules/day-mask.style.css b/frontend/src/use-cases/rules/day-mask.style.css deleted file mode 100644 index ee4eb91..0000000 --- a/frontend/src/use-cases/rules/day-mask.style.css +++ /dev/null @@ -1,30 +0,0 @@ -.day.green { - background: #bada55; -} - -.day.red { - background: #931103; -} - -.day { - width: 11px; - height: 11px; - display: inline-block; - margin: 2px; - border-radius: 2px; -} - -.day-input { - width: 29px; - height: 11px; - display: inline-block; - margin: 2px; - border-radius: 2px; -} - -.mask-container { - min-width: 140px; - padding-left: 0; - margin-left: 0; - justify-content: left; -} diff --git a/frontend/src/use-cases/rules/index.css b/frontend/src/use-cases/rules/index.css deleted file mode 100644 index 9a3a3ec..0000000 --- a/frontend/src/use-cases/rules/index.css +++ /dev/null @@ -1,6 +0,0 @@ -.container { - display: flex; - justify-content: center; - padding-top: 2rem; - width: 100%; -} diff --git a/frontend/src/use-cases/rules/index.jsx b/frontend/src/use-cases/rules/index.jsx deleted file mode 100644 index 34f9284..0000000 --- a/frontend/src/use-cases/rules/index.jsx +++ /dev/null @@ -1,115 +0,0 @@ -import { useContext } from "react"; -import { - DigitCRUD, - useDigitTranslations, - useDigitToast, -} from "@cthit/react-digit-components"; -import DayMask from "./day-mask.element"; -import Rooms from "./rooms.element"; -import CancelIcon from "@material-ui/icons/Cancel"; -import CheckIcon from "@material-ui/icons/Check"; -import "./index.css"; -import { - createRule, - deleteRule, - getRule, - getRules, -} from "../../api/backend.api"; -import { formatDate, formatDT, formatTime } from "../../utils/utils"; -import { detailed_view_keys, table_header_keys } from "./rules.labels"; -import { ruleForm } from "./rule.form"; -import translations from "./rules.translations.json"; -import UserContext from "../../common/contexts/user-context"; - -const formatRule = r => ({ - ...r, - _time: `${r.start_time}-${r.end_time}`, - start_date: formatDate(Number(r.start_date)), - end_date: formatDate(Number(r.end_date)), - _room: <Rooms rooms={r.room.sort()} />, - _day_mask: <DayMask day_mask={r.day_mask} />, - _allow: r.allow ? <CheckIcon /> : <CancelIcon />, - created_at: formatDT(r.created_at), - updated_at: formatDT(r.updated_at), -}); - -const getRulesFormatted = async () => { - const rules = await getRules(); - return rules.map(r => formatRule(r)); -}; - -const getRuleFormatted = async id => { - return { data: formatRule(await getRule(id)) }; -}; - -const Rules = () => { - const [openToast] = useDigitToast({ - duration: 3000, - actionText: "Ok", - actionHandler: () => {}, - }); - const [texts, activeLanguage] = useDigitTranslations(translations); - const [user] = useContext(UserContext); - - const createRuleCallback = async rule => { - const res = await createRule({ - description: rule.description, - priority: Number(rule.priority), - title: rule.title, - allow: rule._allow, - day_mask: rule._day_mask, - room: rule._room, - start_date: formatDate(rule.start_date), - end_date: formatDate(rule.end_date), - start_time: formatTime(rule.start_time), - end_time: formatTime(rule.end_time), - }); - if (res === null) { - return true; - } - openToast({ - text: res[activeLanguage], - }); - }; - - return ( - <div className="container"> - <DigitCRUD - readAllRequest={getRulesFormatted} - readOneRequest={getRuleFormatted} - createRequest={user.is_admin ? createRuleCallback : null} - deleteRequest={user.is_admin ? deleteRule : null} - path="/rules" - idProp="id" - keysOrder={detailed_view_keys} - keysText={texts} - backButtonText={texts.back} - createButtonText={texts.create} - detailsButtonText={texts.details} - createTitle={texts.create_rule} - tableProps={{ - columnsOrder: table_header_keys, - headerTexts: texts, - titleText: texts.Rules, - startOrderBy: "title", - startRowsPerPage: 10, - }} - formInitialValues={{ - title: "", - priority: 10, - start_date: new Date(), - end_date: new Date("2040-12-31"), - start_time: new Date("2021-08-21T08:00"), - end_time: new Date("2021-08-21T17:00"), - _allow: true, - _day_mask: 0, - description: "", - _room: [], - }} - formComponentData={ruleForm(texts)} - /> - </div> - ); -}; - -export default Rules; diff --git a/frontend/src/use-cases/rules/rooms.element.jsx b/frontend/src/use-cases/rules/rooms.element.jsx deleted file mode 100644 index 6de3617..0000000 --- a/frontend/src/use-cases/rules/rooms.element.jsx +++ /dev/null @@ -1,14 +0,0 @@ -import ROOMS from "../../common/rooms"; - -const Rooms = ({ rooms }) => { - return ( - <div> - {rooms.map(r => { - const room = ROOMS.find(e => e.value === r); - return <div key={r}>{room.text}</div>; - })} - </div> - ); -}; - -export default Rooms; diff --git a/frontend/src/use-cases/rules/rule.form.jsx b/frontend/src/use-cases/rules/rule.form.jsx deleted file mode 100644 index b92916f..0000000 --- a/frontend/src/use-cases/rules/rule.form.jsx +++ /dev/null @@ -1,83 +0,0 @@ -import { - DigitTextField, - DigitDatePicker, - DigitTextArea, - DigitTimePicker, - DigitSwitch, - DigitAutocompleteSelectMultiple, -} from "@cthit/react-digit-components"; -import { DayMaskInput } from "./day-mask.element"; -import ROOMS from "../../common/rooms"; - -export const ruleForm = texts => ({ - title: { - component: DigitTextField, - componentProps: { - outlined: true, - upperLabel: texts.title, - }, - }, - priority: { - component: DigitTextField, - componentProps: { - outlined: true, - upperLabel: texts.priority, - }, - }, - start_date: { - component: DigitDatePicker, - componentProps: { - outlined: true, - upperLabel: texts.start_date, - }, - }, - end_date: { - component: DigitDatePicker, - componentProps: { - outlined: true, - upperLabel: texts.end_date, - }, - }, - start_time: { - component: DigitTimePicker, - componentProps: { - outlined: true, - upperLabel: texts.start_time, - }, - }, - end_time: { - component: DigitTimePicker, - componentProps: { - outlined: true, - upperLabel: texts.end_time, - }, - }, - description: { - component: DigitTextArea, - componentProps: { - outlined: true, - upperLabel: texts.description, - rows: 3, - }, - }, - _room: { - component: DigitAutocompleteSelectMultiple, - componentProps: { - options: ROOMS, - upperLabel: texts._room, - }, - }, - _day_mask: { - component: DayMaskInput, - componentProps: { - label: texts._day_mask, - }, - }, - _allow: { - component: DigitSwitch, - componentProps: { - primary: true, - label: texts._allow, - }, - }, -}); diff --git a/frontend/src/use-cases/rules/rules.labels.js b/frontend/src/use-cases/rules/rules.labels.js deleted file mode 100644 index 0c6f0c1..0000000 --- a/frontend/src/use-cases/rules/rules.labels.js +++ /dev/null @@ -1,25 +0,0 @@ -export const detailed_view_keys = [ - "title", - "priority", - "start_date", - "end_date", - "start_time", - "end_time", - "description", - "_room", - "_day_mask", - "_allow", - "created_at", - "updated_at", -]; - -export const table_header_keys = [ - "title", - "priority", - "start_date", - "end_date", - "_time", - "_room", - "_day_mask", - "_allow", -]; diff --git a/frontend/src/use-cases/rules/rules.translations.json b/frontend/src/use-cases/rules/rules.translations.json deleted file mode 100644 index 9e753f3..0000000 --- a/frontend/src/use-cases/rules/rules.translations.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "title": [ - "Title", - "Titel" - ], - "priority": [ - "Priority", - "Prioritet" - ], - "start_date": [ - "Start date", - "Start datum" - ], - "end_date": [ - "End date", - "Slut datum" - ], - "start_time": [ - "Start time", - "Start tid" - ], - "end_time": [ - "End time", - "Slut tid" - ], - "description": [ - "Description", - "Beskrivning" - ], - "_room": [ - "Rooms", - "Rum" - ], - "_day_mask": [ - "M T W T F S S", - "M T O T F L S" - ], - "_allow": [ - "Allow", - "Tillåt" - ], - "_time": [ - "Time", - "Tid" - ], - "created_at": [ - "Created at", - "Skapades" - ], - "updated_at": [ - "Updated at", - "Uppdaterades" - ], - "rules": [ - "Rules", - "Regler" - ], - "back": [ - "Back", - "Tillbaka" - ], - "details": [ - "Details", - "Detaljer" - ], - "create": [ - "Create", - "Skapa" - ], - "create_rule": [ - "Create rule", - "Skapa regel" - ] -} \ No newline at end of file diff --git a/frontend/src/utils/utils.js b/frontend/src/utils/utils.js deleted file mode 100644 index c618969..0000000 --- a/frontend/src/utils/utils.js +++ /dev/null @@ -1,19 +0,0 @@ -import moment from "moment"; - -export const formatDT = date => { - if (Number.isNaN(Number.parseInt(date))) - return moment(date).format("yyyy-MM-DD HH:mm"); - return moment(new Date(Number.parseInt(date))).format("yyyy-MM-DD HH:mm"); -}; -export const formatDate = date => moment(date).format("yyyy-MM-DD"); -export const formatTime = date => moment(date).format("HH:mm"); - -// Returns true if one or more items are equal in a and b -export const overlap = (a, b) => { - for (const i in b) { - if (a.includes(b[i])) { - return true; - } - } - return false; -}; diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 0000000..bc2d8a3 --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// <reference types="vite-plus/client" /> diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..61c8a98 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2023", + "lib": ["ES2023", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "Bundler", + "jsx": "react-jsx", + "strict": true, + "noUncheckedIndexedAccess": true, + "noFallthroughCasesInSwitch": true, + "noEmit": true, + "skipLibCheck": true, + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "paths": { "@/*": ["./src/*"] } + }, + "include": ["src", "vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..2b889a0 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,14 @@ +import { fileURLToPath, URL } from "node:url"; +import { defineConfig } from "vite-plus"; +import react from "@vitejs/plugin-react"; +import tailwindcss from "@tailwindcss/vite"; +export default defineConfig({ + plugins: [react(), tailwindcss()], + resolve: { alias: { "@": fileURLToPath(new URL("./src", import.meta.url)) } }, + server: { + host: "127.0.0.1", + port: 3001, + strictPort: true, + proxy: { "/api": process.env.BACKEND_URL ?? "http://127.0.0.1:8080" }, + }, +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000..489b401 --- /dev/null +++ b/package.json @@ -0,0 +1,37 @@ +{ + "name": "bookit-node", + "private": true, + "scripts": { + "codegen": "tsx codegen.ts", + "build": "pnpm codegen && pnpm --dir backend exec prisma generate && pnpm --dir backend build && pnpm --dir frontend build", + "test": "vp test run", + "check": "vp check", + "lint": "vp lint --type-aware", + "format": "vp fmt", + "format:check": "vp fmt --check", + "typecheck": "pnpm --dir backend typecheck && pnpm --dir frontend typecheck && tsc --noEmit -p e2e/tsconfig.json", + "test:e2e": "playwright test", + "e2e:dev": "tsx e2e/dev.ts" + }, + "devDependencies": { + "@graphql-codegen/core": "^6.2.0", + "@graphql-codegen/typed-document-node": "^7.1.0", + "@graphql-codegen/typescript": "^6.1.0", + "@graphql-codegen/typescript-operations": "^6.1.6", + "@graphql-codegen/typescript-resolvers": "^6.1.0", + "@graphql-tools/load-files": "^7.0.1", + "@graphql-tools/merge": "^9.2.3", + "@playwright/test": "^1.63.0", + "@testcontainers/postgresql": "^12.1.0", + "@types/node": "^24.13.3", + "graphql": "^16.14.2", + "testcontainers": "^12.1.0", + "tsx": "^4.23.13", + "typescript": "^7.0.2", + "vite-plus": "0.3.0" + }, + "engines": { + "node": ">=24" + }, + "packageManager": "pnpm@12.3.4" +} diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..0bb42e2 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,14 @@ +import { defineConfig, devices } from "@playwright/test"; +export default defineConfig({ + testDir: "./e2e", + testMatch: "**/*.spec.ts", + fullyParallel: false, + workers: 1, + forbidOnly: Boolean(process.env.CI), + retries: process.env.CI ? 1 : 0, + timeout: 90_000, + expect: { timeout: 15_000 }, + reporter: [["list"], ["html", { open: "never" }]], + use: { trace: "retain-on-failure", screenshot: "only-on-failure", video: "retain-on-failure" }, + projects: [{ name: "chromium", use: { ...devices["Desktop Chrome"] } }], +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..5dd8756 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,9728 @@ +--- +lockfileVersion: '9.0' + +importers: + + .: + configDependencies: {} + packageManagerDependencies: + pnpm: + specifier: 12.3.4 + version: 12.3.4 + +packages: + + '@pnpm/exe.darwin-arm64@12.3.4': + resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==} + cpu: [arm64] + os: [darwin] + + '@pnpm/exe.darwin-x64@12.3.4': + resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==} + cpu: [x64] + os: [darwin] + + '@pnpm/exe.linux-arm64-musl@12.3.4': + resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@pnpm/exe.linux-arm64@12.3.4': + resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-x64-musl@12.3.4': + resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@pnpm/exe.linux-x64@12.3.4': + resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.win32-arm64@12.3.4': + resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==} + cpu: [arm64] + os: [win32] + + '@pnpm/exe.win32-x64@12.3.4': + resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==} + cpu: [x64] + os: [win32] + + pnpm@12.3.4: + resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==} + engines: {node: '>=18.*'} + hasBin: true + +snapshots: + + '@pnpm/exe.darwin-arm64@12.3.4': + optional: true + + '@pnpm/exe.darwin-x64@12.3.4': + optional: true + + '@pnpm/exe.linux-arm64-musl@12.3.4': + optional: true + + '@pnpm/exe.linux-arm64@12.3.4': + optional: true + + '@pnpm/exe.linux-x64-musl@12.3.4': + optional: true + + '@pnpm/exe.linux-x64@12.3.4': + optional: true + + '@pnpm/exe.win32-arm64@12.3.4': + optional: true + + '@pnpm/exe.win32-x64@12.3.4': + optional: true + + pnpm@12.3.4: + optionalDependencies: + '@pnpm/exe.darwin-arm64': 12.3.4 + '@pnpm/exe.darwin-x64': 12.3.4 + '@pnpm/exe.linux-arm64': 12.3.4 + '@pnpm/exe.linux-arm64-musl': 12.3.4 + '@pnpm/exe.linux-x64': 12.3.4 + '@pnpm/exe.linux-x64-musl': 12.3.4 + '@pnpm/exe.win32-arm64': 12.3.4 + '@pnpm/exe.win32-x64': 12.3.4 + +--- +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +overrides: + vite: npm:@voidzero-dev/vite-plus-core@0.3.0 + vitest: 4.1.11 + '@prisma/config>deepmerge-ts': 8.0.0 + prisma>mysql2: 3.24.3 + archiver-utils>glob: 13.0.6 + +importers: + + .: + devDependencies: + '@graphql-codegen/core': + specifier: ^6.2.0 + version: 6.2.0(graphql@16.14.2) + '@graphql-codegen/typed-document-node': + specifier: ^7.1.0 + version: 7.1.0(graphql@16.14.2) + '@graphql-codegen/typescript': + specifier: ^6.1.0 + version: 6.1.0(graphql@16.14.2) + '@graphql-codegen/typescript-operations': + specifier: ^6.1.6 + version: 6.1.6(graphql@16.14.2) + '@graphql-codegen/typescript-resolvers': + specifier: ^6.1.0 + version: 6.1.0(graphql@16.14.2) + '@graphql-tools/load-files': + specifier: ^7.0.1 + version: 7.0.1(graphql@16.14.2) + '@graphql-tools/merge': + specifier: ^9.2.3 + version: 9.2.3(graphql@16.14.2) + '@playwright/test': + specifier: ^1.63.0 + version: 1.63.0 + '@testcontainers/postgresql': + specifier: ^12.1.0 + version: 12.1.0(supports-color@8.1.1) + '@types/node': + specifier: ^24.13.3 + version: 24.13.3 + graphql: + specifier: ^16.14.2 + version: 16.14.2 + testcontainers: + specifier: ^12.1.0 + version: 12.1.0(supports-color@8.1.1) + tsx: + specifier: ^4.23.13 + version: 4.23.13 + typescript: + specifier: ^7.0.2 + version: 7.0.2 + vite-plus: + specifier: 0.3.0 + version: 0.3.0(@types/node@24.13.3)(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0) + + backend: + dependencies: + '@apollo/server': + specifier: ^5.5.1 + version: 5.5.1(graphql@16.14.2)(supports-color@8.1.1) + '@as-integrations/express5': + specifier: ^1.1.2 + version: 1.1.2(@apollo/server@5.5.1(graphql@16.14.2)(supports-color@8.1.1))(express@5.2.1(supports-color@8.1.1)) + '@graphql-tools/load-files': + specifier: ^7.0.1 + version: 7.0.1(graphql@16.14.2) + '@graphql-tools/merge': + specifier: ^9.2.3 + version: 9.2.3(graphql@16.14.2) + '@graphql-tools/schema': + specifier: ^10.1.0 + version: 10.1.0(graphql@16.14.2) + '@prisma/adapter-pg': + specifier: ^7.10.0 + version: 7.10.0 + '@prisma/client': + specifier: ^7.10.0 + version: 7.10.0(prisma@7.10.0(@types/node@24.13.3)(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2))(typescript@7.0.2) + axios: + specifier: ^1.20.0 + version: 1.20.0(debug@4.4.3(supports-color@8.1.1))(supports-color@8.1.1) + dotenv: + specifier: ^17.4.2 + version: 17.4.2 + express: + specifier: ^5.2.1 + version: 5.2.1(supports-color@8.1.1) + express-http-proxy: + specifier: ^2.1.2 + version: 2.1.2(supports-color@8.1.1) + express-openid-connect: + specifier: ^3.4.0 + version: 3.4.0(express@5.2.1(supports-color@8.1.1))(supports-color@8.1.1) + graphql: + specifier: ^16.12.0 + version: 16.14.2 + pg: + specifier: ^8.16.3 + version: 8.23.0 + prisma: + specifier: ^7.10.0 + version: 7.10.0(@types/node@24.13.3)(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2) + redis: + specifier: ^6.2.1 + version: 6.2.1 + tsx: + specifier: ^4.21.0 + version: 4.23.13 + devDependencies: + '@types/express': + specifier: ^5.0.6 + version: 5.0.6 + '@types/express-http-proxy': + specifier: ^1.6.7 + version: 1.6.7 + '@types/node': + specifier: ^24.10.1 + version: 24.13.3 + '@types/pg': + specifier: ^8.15.6 + version: 8.23.1 + typescript: + specifier: ^7.0.2 + version: 7.0.2 + + frontend: + dependencies: + '@fullcalendar/react': + specifier: 7.1.0 + version: 7.1.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(temporal-polyfill@1.0.4) + '@graphql-typed-document-node/core': + specifier: ^3.2.0 + version: 3.2.0(graphql@16.14.2) + '@tanstack/react-query': + specifier: ^5.102.8 + version: 5.102.8(react@19.2.8) + '@tanstack/react-router': + specifier: ^1.170.32 + version: 1.170.32(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + cn: + specifier: ^0.2.5 + version: 0.2.5 + date-fns: + specifier: ^4.4.0 + version: 4.4.0 + graphql: + specifier: ^16.12.0 + version: 16.14.2 + lucide-react: + specifier: ^1.41.0 + version: 1.41.0(react@19.2.8) + radix-ui: + specifier: ^1.6.7 + version: 1.6.7(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: + specifier: ^19.2.8 + version: 19.2.8 + react-dom: + specifier: ^19.2.8 + version: 19.2.8(react@19.2.8) + sonner: + specifier: ^2.0.8 + version: 2.0.8(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + tailwind-merge: + specifier: ^3.6.0 + version: 3.6.0 + temporal-polyfill: + specifier: ^1.0.4 + version: 1.0.4 + devDependencies: + '@tailwindcss/vite': + specifier: ^4.3.3 + version: 4.3.3(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0)) + '@types/react': + specifier: ^19.2.18 + version: 19.2.18 + '@types/react-dom': + specifier: ^19.2.7 + version: 19.2.7(@types/react@19.2.18) + '@vitejs/plugin-react': + specifier: ^6.1.1 + version: 6.1.1(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0)) + tailwindcss: + specifier: ^4.3.3 + version: 4.3.3 + tw-animate-css: + specifier: ^1.4.0 + version: 1.4.0 + typescript: + specifier: ^7.0.2 + version: 7.0.2 + vite-plus: + specifier: 0.3.0 + version: 0.3.0(@types/node@24.13.3)(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0) + +packages: + + '@apollo/cache-control-types@1.0.3': + resolution: {integrity: sha512-F17/vCp7QVwom9eG7ToauIKdAxpSoadsJnqIfyryLFSkLSOEqu+eC5Z3N8OXcUVStuOMcNHlyraRsA6rRICu4g==} + peerDependencies: + graphql: 14.x || 15.x || 16.x + + '@apollo/protobufjs@1.2.8': + resolution: {integrity: sha512-r7xNeUqZX+eBBEmyvaPw0/cSz6zgf5jdH8mjUz8ynKpNs/GU7vi2T7sNcZINk2ZID7wwjG91FCgdpCrQuJ8rzA==} + hasBin: true + + '@apollo/server-gateway-interface@2.0.0': + resolution: {integrity: sha512-3HEMD6fSantG2My3jWkb9dvfkF9vJ4BDLRjMgsnD790VINtuPaEp+h3Hg9HOHiWkML6QsOhnaRqZ+gvhp3y8Nw==} + peerDependencies: + graphql: 14.x || 15.x || 16.x + + '@apollo/server@5.5.1': + resolution: {integrity: sha512-Rn3g5TJQsMSUY23CWZTghWdBWyjX7dP1eaEBPkvmM2RHi82cDcpgTIkSCbGvtTUEGjwopLv1AAooU/n7iIZ20A==} + engines: {node: '>=20'} + peerDependencies: + graphql: ^16.11.0 + + '@apollo/usage-reporting-protobuf@4.1.2': + resolution: {integrity: sha512-aTnAD41RYz0d5dawlyR5Iclkgzx0Xb0njUJmEfvZ6pS4f4HU8wCYyctPpWat/HWp2PmRwDfX5R1k4uVcDKZ4xA==} + + '@apollo/utils.createhash@3.0.1': + resolution: {integrity: sha512-CKrlySj4eQYftBE5MJ8IzKwIibQnftDT7yGfsJy5KSEEnLlPASX0UTpbKqkjlVEwPPd4mEwI7WOM7XNxEuO05A==} + engines: {node: '>=16'} + + '@apollo/utils.dropunuseddefinitions@2.0.1': + resolution: {integrity: sha512-EsPIBqsSt2BwDsv8Wu76LK5R1KtsVkNoO4b0M5aK0hx+dGg9xJXuqlr7Fo34Dl+y83jmzn+UvEW+t1/GP2melA==} + engines: {node: '>=14'} + peerDependencies: + graphql: 14.x || 15.x || 16.x + + '@apollo/utils.fetcher@3.1.0': + resolution: {integrity: sha512-Z3QAyrsQkvrdTuHAFwWDNd+0l50guwoQUoaDQssLOjkmnmVuvXlJykqlEJolio+4rFwBnWdoY1ByFdKaQEcm7A==} + engines: {node: '>=16'} + + '@apollo/utils.isnodelike@3.0.0': + resolution: {integrity: sha512-xrjyjfkzunZ0DeF6xkHaK5IKR8F1FBq6qV+uZ+h9worIF/2YSzA0uoBxGv6tbTeo9QoIQnRW4PVFzGix5E7n/g==} + engines: {node: '>=16'} + + '@apollo/utils.keyvaluecache@4.0.0': + resolution: {integrity: sha512-mKw1myRUkQsGPNB+9bglAuhviodJ2L2MRYLTafCMw5BIo7nbvCPNCkLnIHjZ1NOzH7SnMAr5c9LmXiqsgYqLZw==} + engines: {node: '>=20'} + + '@apollo/utils.logger@3.0.0': + resolution: {integrity: sha512-M8V8JOTH0F2qEi+ktPfw4RL7MvUycDfKp7aEap2eWXfL5SqWHN6jTLbj5f5fj1cceHpyaUSOZlvlaaryaxZAmg==} + engines: {node: '>=16'} + + '@apollo/utils.printwithreducedwhitespace@2.0.1': + resolution: {integrity: sha512-9M4LUXV/fQBh8vZWlLvb/HyyhjJ77/I5ZKu+NBWV/BmYGyRmoEP9EVAy7LCVoY3t8BDcyCAGfxJaLFCSuQkPUg==} + engines: {node: '>=14'} + peerDependencies: + graphql: 14.x || 15.x || 16.x + + '@apollo/utils.removealiases@2.0.1': + resolution: {integrity: sha512-0joRc2HBO4u594Op1nev+mUF6yRnxoUH64xw8x3bX7n8QBDYdeYgY4tF0vJReTy+zdn2xv6fMsquATSgC722FA==} + engines: {node: '>=14'} + peerDependencies: + graphql: 14.x || 15.x || 16.x + + '@apollo/utils.sortast@2.0.1': + resolution: {integrity: sha512-eciIavsWpJ09za1pn37wpsCGrQNXUhM0TktnZmHwO+Zy9O4fu/WdB4+5BvVhFiZYOXvfjzJUcc+hsIV8RUOtMw==} + engines: {node: '>=14'} + peerDependencies: + graphql: 14.x || 15.x || 16.x + + '@apollo/utils.stripsensitiveliterals@2.0.1': + resolution: {integrity: sha512-QJs7HtzXS/JIPMKWimFnUMK7VjkGQTzqD9bKD1h3iuPAqLsxd0mUNVbkYOPTsDhUKgcvUOfOqOJWYohAKMvcSA==} + engines: {node: '>=14'} + peerDependencies: + graphql: 14.x || 15.x || 16.x + + '@apollo/utils.usagereporting@2.1.0': + resolution: {integrity: sha512-LPSlBrn+S17oBy5eWkrRSGb98sWmnEzo3DPTZgp8IQc8sJe0prDgDuppGq4NeQlpoqEHz0hQeYHAOA0Z3aQsxQ==} + engines: {node: '>=14'} + peerDependencies: + graphql: 14.x || 15.x || 16.x + + '@apollo/utils.withrequired@3.0.0': + resolution: {integrity: sha512-aaxeavfJ+RHboh7c2ofO5HHtQobGX4AgUujXP4CXpREHp9fQ9jPi6K9T1jrAKe7HIipoP0OJ1gd6JamSkFIpvA==} + engines: {node: '>=16'} + + '@ardatan/relay-compiler@13.0.2': + resolution: {integrity: sha512-VFpv9UP820SiwDUPYtq7PmD3jifzZlevkQ26bhbSzFeruSTys0eHzQCZyKg+IhgmZzwPI9AFjPe26ABNjGeIKg==} + peerDependencies: + graphql: '*' + + '@as-integrations/express5@1.1.2': + resolution: {integrity: sha512-BxfwtcWNf2CELDkuPQxi5Zl3WqY/dQVJYafeCBOGoFQjv5M0fjhxmAFZ9vKx/5YKKNeok4UY6PkFbHzmQrdxIA==} + engines: {node: '>=20'} + peerDependencies: + '@apollo/server': ^4.0.0 || ^5.0.0 + express: ^5.0.0 + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + + '@babel/runtime@8.0.0': + resolution: {integrity: sha512-sL6cvO2IfkSu/iU+zs2S/w01B7A8V7suXSIKEN4hPFFdZoiPGxrj5pAG0lCaqLWiEIrjKzdznIWuaLcxPR53qw==} + + '@balena/dockerignore@1.0.2': + resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==} + + '@blazediff/core@1.9.1': + resolution: {integrity: sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==} + + '@electric-sql/pglite-socket@0.1.3': + resolution: {integrity: sha512-LAciWM0M1dCL8hlsxu2venbVZcdxema0BtDfpWYVqr+Y468UADw0pFWidhKw1M8sfJ8rdLT71tjMmnirf/IZRQ==} + hasBin: true + peerDependencies: + '@electric-sql/pglite': 0.4.3 + + '@electric-sql/pglite-tools@0.3.3': + resolution: {integrity: sha512-AlzLJTRJ8+UFgK8CmxIpyIpJ0+YaFw02IiOSdYrqxwPXdSyeIShz8aa9Tq+tYFXdPwcaMp/Fc80mQZ1dkOQ/wg==} + peerDependencies: + '@electric-sql/pglite': 0.4.3 + + '@electric-sql/pglite@0.4.3': + resolution: {integrity: sha512-ichuWTgtd4mOM1G4SpyGJa5trT03lWbMypDV0fUXUCXg5hiHqVAz/bZyV68NqmkLB7WcYmj1RMJVSp8HV/v/ZQ==} + + '@esbuild/aix-ppc64@0.28.2': + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.28.2': + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.28.2': + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.28.2': + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.28.2': + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.2': + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.28.2': + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.2': + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.28.2': + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.28.2': + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.28.2': + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.28.2': + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.28.2': + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.28.2': + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.2': + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.2': + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.2': + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.2': + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.2': + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.2': + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.2': + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.2': + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.2': + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.2': + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.2': + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@floating-ui/core@1.8.0': + resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==} + + '@floating-ui/dom@1.8.0': + resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==} + + '@floating-ui/react-dom@2.1.9': + resolution: {integrity: sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.12': + resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} + + '@full-ui/headless-calendar@7.1.0': + resolution: {integrity: sha512-T54tSUik/ia9Jj4xduFHv1p98eVBgLnE33Q6qhpg+WWkqKCLBCbtVyMcTPPIOP5jjTV2MbtjrRvRt1RLSryGHA==} + peerDependencies: + temporal-polyfill: ^1.0.1 + + '@fullcalendar/core@7.1.0': + resolution: {integrity: sha512-a+ABP7zQpPWpwPT8EsOdShpeOVrJyx/XYStqERd8ibld/FLi/dzpd95r7w3RIdFqFm9iUv0gdGvBshSlI/dbPw==} + peerDependencies: + '@full-ui/headless-calendar': 7.1.0 + temporal-polyfill: ^1.0.1 + + '@fullcalendar/react@7.1.0': + resolution: {integrity: sha512-FI5hTnbiVLcW+/ZnPKteBgLpK/ldyTaSDWn9A3ri3vLToAbGt49JqRhQ9CMpH+nhmuNX4ihnCQcVzoqVCsIbBw==} + peerDependencies: + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + temporal-polyfill: ^1.0.1 + + '@graphql-codegen/core@6.2.0': + resolution: {integrity: sha512-RZadhhwYhuy2ZdIGK40vYVBMzXEFGkCC+58MUC/F2af/gKznEYNzHgmNBUBCk/BTklyUsNu0mIXmyGE4tTA0PA==} + engines: {node: '>=16'} + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-codegen/plugin-helpers@7.3.0': + resolution: {integrity: sha512-aaGfI/8Q8wmdO1nx1NMWGoZn/5Tl5TNOVQSI2qkGhk8h8Z8Uc6grFH0O6//HdHfqnUsiruBbe3IaK4/ah3sWcQ==} + engines: {node: '>=16'} + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-codegen/schema-ast@6.1.0': + resolution: {integrity: sha512-/xuGkM5gUNFRoaQLumKbENdX7Hc8ha49z9OXsEZY8E+46mMjqzXGF0NtCJ892cmoX7EUgI5c8T+LZqS2upx2Aw==} + engines: {node: '>=16'} + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-codegen/typed-document-node@7.1.0': + resolution: {integrity: sha512-V6H+ItyqXtYY+JQb76LAoN627Xfzpn29/ifwCFAv61iEepzNzh86sa+yZclflr0G8LDmhcVY5hpPJd3a1qbOfw==} + engines: {node: '>=16'} + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-codegen/typescript-operations@6.1.6': + resolution: {integrity: sha512-qsYRkK27YbSL2doifPvldVwdwHRQtpsxXJINs22wQWGksC1Mrgn9YNaAGUZ2so46U20TKTm4SBoiNXRJE3esqA==} + engines: {node: '>=16'} + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + graphql-sock: ^1.0.0 + peerDependenciesMeta: + graphql-sock: + optional: true + + '@graphql-codegen/typescript-resolvers@6.1.0': + resolution: {integrity: sha512-5I8ymPQZEh2ShDaOrFcrF6DQaJQ5Z0hEIqYwMW8VboH4N8OMdyRZ3vMbiEPdfS+rfaoCf+f660+hKfrmK+JGuQ==} + engines: {node: '>=16'} + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + graphql-sock: ^1.0.0 + peerDependenciesMeta: + graphql-sock: + optional: true + + '@graphql-codegen/typescript@6.1.0': + resolution: {integrity: sha512-2Hu3111O/AwV28Ap7tNsixlmXSAJuQbQArQklx+IC/tNswpckZnCfmlcBtTJrGU1+mJXEneJXGfb2XWvKjbhlQ==} + engines: {node: '>=16'} + peerDependencies: + graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-codegen/visitor-plugin-common@7.2.5': + resolution: {integrity: sha512-hqOemBp0Ue+WSmk8EDcKP+lghuLC2zsI+jHzSUuoffTZnEN2qZH4VP2C9c1fVd2yy6v+YQiiObOarTMaqF4q9w==} + engines: {node: '>=16'} + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-tools/load-files@7.0.1': + resolution: {integrity: sha512-oTNIENc9To9u8Gc3kY82C74caW6kXa8ya2GyxWRXp8gP4zK/7PmvlWJK0/GFCUH0cU3t9jM7k59zXz1+ZfP3Mw==} + engines: {node: '>=16.0.0'} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-tools/merge@9.2.3': + resolution: {integrity: sha512-cKRoXqJGy2zSRBLvotQpkACbXlHAb0yuHLN0l0ypKGCuL3NnF0zofYalkBJqiBxxxpK0lr3s9uowKFHCKi1/ZQ==} + engines: {node: '>=16.0.0'} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-tools/optimize@2.0.0': + resolution: {integrity: sha512-nhdT+CRGDZ+bk68ic+Jw1OZ99YCDIKYA5AlVAnBHJvMawSx9YQqQAIj4refNc1/LRieGiuWvhbG3jvPVYho0Dg==} + engines: {node: '>=16.0.0'} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-tools/relay-operation-optimizer@7.1.9': + resolution: {integrity: sha512-sbkTNbuwT0iGSaQ5hc7dWzfmxTOPVXMZhb5TmdrIWVqhbw8sRenwd/tGCF6jFTG67CSCm0zXOmb9FJhmdM3FmA==} + engines: {node: '>=16.0.0'} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-tools/schema@10.1.0': + resolution: {integrity: sha512-wao48XQnfY631s3jXoNrhEHvCI8mlKXmIuWrR7F6zAdv92VuSOfHoq9P9KL2EnUMgBUnaStnByOx9Mn6RieWDg==} + engines: {node: '>=16.0.0'} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-tools/utils@11.2.2': + resolution: {integrity: sha512-Do2A/t6ayqOma8m7PvpYMsCBswL+NB35BQSng4GWSBqafL2/BnfAZy/NSENPwV721Rm2fG0BHETFC0+FJJZmLw==} + engines: {node: '>=16.0.0'} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-tools/utils@12.0.0': + resolution: {integrity: sha512-aMdIo/l+8j4lhamWAf+MWHr+lOW8zArSBKXspqtKHgt5I2JF9LS55KDLUe/L9AiJOV/O6qJJ60hgYydbnJHbxg==} + engines: {node: '>=16.0.0'} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-typed-document-node/core@3.2.0': + resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==} + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@grpc/grpc-js@1.14.4': + resolution: {integrity: sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==} + engines: {node: '>=12.10.0'} + + '@grpc/proto-loader@0.7.15': + resolution: {integrity: sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==} + engines: {node: '>=6'} + hasBin: true + + '@grpc/proto-loader@0.8.1': + resolution: {integrity: sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==} + engines: {node: '>=6'} + hasBin: true + + '@hapi/hoek@9.3.0': + resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} + + '@hapi/topo@5.1.0': + resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} + + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@js-sdsl/ordered-map@4.4.2': + resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + + '@kwsites/file-exists@1.1.1': + resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@oxc-project/runtime@0.146.0': + resolution: {integrity: sha512-lbXHIpZ1MmK6zuw5txlMdIZ2waLVUIU5Gnm3sEuwJOiqDfQfbtjeHscatmeBoxbv8+If9LFM6PGh/3DcDWYIYw==} + engines: {node: ^20.19.0 || >=22.12.0} + + '@oxc-project/types@0.146.0': + resolution: {integrity: sha512-XC0QsnnhVe7sLIWmYmdPw7x5P0h4W8vUU3Nv1ySgWXtvCz8NizoAEpGXA0sOYoJQV2Rl13LgURAHQ5cI5ILCSA==} + + '@oxfmt/binding-android-arm-eabi@0.64.0': + resolution: {integrity: sha512-o6uzh/jTOQeAY5TdkAeXdqv7MBRcPxiRA08zrcBtkKj5cSu/FMu0Hl7Q6Fi1KCKyCWZ6lJVjBzdsJvsKltUsGQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxfmt/binding-android-arm64@0.64.0': + resolution: {integrity: sha512-jRGSUeeP7p3Gynw2YaCVtjBIA6ZxY6bEB/ES5i54OhqmRTyuVg7ZgstEtzgq6GOAJd+2QZ5pvf+bFfmW5Mp9cw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxfmt/binding-darwin-arm64@0.64.0': + resolution: {integrity: sha512-JINwtU2lW7nOFSqi+H2qplipNUqah9Gc1jgGmB82kTD4UnZrZIVxCJ9qEmFiKfjNq27gYLFhrUb0to86aCwMjw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxfmt/binding-darwin-x64@0.64.0': + resolution: {integrity: sha512-gCmuswrgrOSajV4HCRFkVCGIruPq8bjYuPYgSE2WQB3mD6XrdyZ3JMSRZCkQ8zCxOyGWriBo6QoZ5nmMHQ1BfA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxfmt/binding-freebsd-x64@0.64.0': + resolution: {integrity: sha512-Ab8g7a38pT0MMImjh7anRSTve6buWBIlcXIFBYa5xl4s6UxEgKSc2xOOhbGtLwvXnEi2PsEDGoJh3oUU7xkehQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxfmt/binding-linux-arm-gnueabihf@0.64.0': + resolution: {integrity: sha512-BgvS3CoQ+Xy2deoZqEN8JVKabcCZi2RxA3yant8G9OAv9KuPJ9TCjHkqigzdHUVwErZxEP5d2bzLIEyKYyBDLg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm-musleabihf@0.64.0': + resolution: {integrity: sha512-QXpNxwoMj0YvnceCNZadNSden3bIcnvjn/sDp/rwZhRoZoZYGpHvtPyhGsdJz9uvT9GkaMW7SsLddurU56dt8w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm64-gnu@0.64.0': + resolution: {integrity: sha512-BBgH3I1ppDsI5pZ4Pdhw0ceYxwVCfbU/bZEBCeZ6caRS9x0ZabErxubP7riGUn11PXZBhe8DYdjkDKP1FlVQ5w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-arm64-musl@0.64.0': + resolution: {integrity: sha512-v19HSjC/BGXdt26qEvKZtwAHgGmQ2Agcap2kQP+KIqoRZqivVzYth3ui2dJA1i+6/fjpjga85lIOaJJjQ/bOOw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-ppc64-gnu@0.64.0': + resolution: {integrity: sha512-PElLnOo4xFTBZrxPhgTIj0eHqZXwEBQoNWtb7facUV170T0B0FRET0iNbb3LUeLWTybkUW+vsdyv4ihOdyXGyw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-gnu@0.64.0': + resolution: {integrity: sha512-Qzsg15n4F5CH+MorcRW4MkAEMiLzXmeG+DiDSbP/bBTqCmWOH3K9DHryNrve+JHlV0txS+B6Z9P5Xz+cmWeL+g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-musl@0.64.0': + resolution: {integrity: sha512-/GZ358wnQ/Ez4UVnCcZIi56JkY0sOdZ+B108pqXKqZz3jLS59F4KEAB1Qv3fRlObrFEk+3L2vUQ/xoPx+3vjXw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-s390x-gnu@0.64.0': + resolution: {integrity: sha512-/C9We3DXegowfLXtVCYHeNiU9azwCDr5cQkEtCVlc74vyn+lLQSPApJ1CZmxAduqeq/Oi3gQ+IVptyhCaTMtkQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-gnu@0.64.0': + resolution: {integrity: sha512-91KM2CeRWscIEHlj1NsW2WSnzGeq1Ehq+39bfDowTdkn+fcvK/x4Y1RcyqT7glyBjZio0ldkeCG6Usj3v7ASog==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-musl@0.64.0': + resolution: {integrity: sha512-gw7uEk9I+7zoT1EYLra1eWArIzNcz8e3jkv+Noo2+o2T7wPvsNSQbfoa4DSfZlvn1i6mJ05RiZ4/omaXPDNhQg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-openharmony-arm64@0.64.0': + resolution: {integrity: sha512-HYHFf616FHSPSO07c09mjmXBfQ73wIVM3m0txOiooa5XZkGoxFd6B14PVj0LB0DXIqJ6wAO/dDR/NX/5UUaqnw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxfmt/binding-win32-arm64-msvc@0.64.0': + resolution: {integrity: sha512-uQjFp081IZSWD6VAofX2iO2z01awAdHmfC+NrieWIPKrT2hZKQDyq/U18M7ifC0sm0Wz8aHY/p6+FDYIzs/CrQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxfmt/binding-win32-ia32-msvc@0.64.0': + resolution: {integrity: sha512-lNM6byTAQ881jugzFu8juJTbNRgsUTlswMA6pJmwi1XDvmIqnnb49lcUAs5gz94fCJLrVN+/X3s3jOKqx23WIQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxfmt/binding-win32-x64-msvc@0.64.0': + resolution: {integrity: sha512-BtmbtL/QjMtF1a6C3CqoDluH2IfB6fJt62E+B9RFfUPtFk4Iz9PFS6+y/SzzOvSxc7aUk2Kphwg7Dh8lMbwu6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxlint-tsgolint/darwin-arm64@7.0.2001': + resolution: {integrity: sha512-CUJEdbSZ54+Xy9OXqOhWLTKZKV0BBiV7C2i/ygyVmXtkUNXx5YCzN8DpSSshTAKktoL7S+tnQ/ftFG/i7X896w==} + cpu: [arm64] + os: [darwin] + + '@oxlint-tsgolint/darwin-x64@7.0.2001': + resolution: {integrity: sha512-pXfBb5BqONCcgrXQNUZWXgiYmRSWJzd97S8i41VVOh6ut0tyo+cJ5FKFpczDHxiVNfj/3e7c9B4MtztNdpIVCw==} + cpu: [x64] + os: [darwin] + + '@oxlint-tsgolint/linux-arm64@7.0.2001': + resolution: {integrity: sha512-roP7zujb/QDPzDwEKsFFpzNHHy91/Y7oX9vQXk78ekyZtcQj1QXDIMH33gjDdHBfRl4K9pZ36xhRgrP4Zr+R8A==} + cpu: [arm64] + os: [linux] + + '@oxlint-tsgolint/linux-x64@7.0.2001': + resolution: {integrity: sha512-UDezNqdECVmngu2TPnjaS1YoAmcTaBoI5lV9vk3VahBxoi+I5r9k3iJTT7qZoYWOXTD/7T7bNcwRgrocR6BscQ==} + cpu: [x64] + os: [linux] + + '@oxlint-tsgolint/win32-arm64@7.0.2001': + resolution: {integrity: sha512-uJZhqB6pdXLuN+AD1F5082byyQti/NPmJA77GtcFlmT2HzRelqbNls3SaIqxpjdFgvSBF9g0yOKGBkGFg7kX8Q==} + cpu: [arm64] + os: [win32] + + '@oxlint-tsgolint/win32-x64@7.0.2001': + resolution: {integrity: sha512-FkDRm8hx9OwzGQqyWG1tO5QrTLRApff9DzSgpz9QZau37BR8d1VYKOxMLGf6shPZntJFoTwIIJYT68VndYDCog==} + cpu: [x64] + os: [win32] + + '@oxlint/binding-android-arm-eabi@1.79.0': + resolution: {integrity: sha512-TebFaaMklO/RXzTv7PucaCq9l3X6D1gA+C8H6K4njtjFOV+zWE9MKLpulcJZN9bzytbUbQIY0mZuz12nQ5Kv4Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxlint/binding-android-arm64@1.79.0': + resolution: {integrity: sha512-KqqnOtAVgNsPPF0YSodkFZA1O80jcKoCZCTu3bgsszxA+MrMP9TLzfXitKjEj1FmrPprKDMdRDMmY3weESO9sg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxlint/binding-darwin-arm64@1.79.0': + resolution: {integrity: sha512-BVC2nsMzqQzRDPc5RhixkZ+m1p7iH4bxRRvqkbwDXX0PlQKm1BPy8J8cRjnAFafOq2QzI+BfO3vE8w2GZ3CBag==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxlint/binding-darwin-x64@1.79.0': + resolution: {integrity: sha512-p6Lm+snmhGuLKL1+CpCV8L6ijkE/qJzK2H2jG9+eKJT0n31RbY4FLsdhexekgP3bLpw4Kgde+9DZuDZQ4yIInA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxlint/binding-freebsd-x64@1.79.0': + resolution: {integrity: sha512-qDMm0dXZnoHyRqSL4N4xUq82T4sqK5cbKSjvd/dF/YbMUXc2R1wEPf+vmA5S0qUmi0nwXfNbjXBtZaIqzQLIMg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxlint/binding-linux-arm-gnueabihf@1.79.0': + resolution: {integrity: sha512-2od7s0nuKPzqyUZAWk9KkCyGg7eI9dwFPZg+20lB15fKFkVZ0c9ZFxqPfiBAyDTlTkh9stPI0t+JlPCqMbItVA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm-musleabihf@1.79.0': + resolution: {integrity: sha512-ZOQUjkzDnvlhSE3+tWC3YXx94MMl+sYMlwH+u1+YGApGHOJP/YAc8ZBRFOXZ6eOBmxtXAWuS/fBcdZr8qqNO1A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm64-gnu@1.79.0': + resolution: {integrity: sha512-lu158FR4nGqGeRS3BQvtG85wRgU/Fy4MD5Cxp1hzJXizGiLo6u2742wJSCDKh8cFcZntvX7fcxlq4mMmfryH1g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-arm64-musl@1.79.0': + resolution: {integrity: sha512-mbpKQeE2aflTjddaHK7MP8KP/OFbUM++lt5M635ENM8IyIdK0jm2t9pb+2v9mVVIvhF6TqA4l7F79Pll1mi+uw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-ppc64-gnu@1.79.0': + resolution: {integrity: sha512-WpGNua7gaxaHnpSDeog2ji8IDHn/QLPl9LPzwkR/FvVv58vT5BcXjRXnU+wbu3N75cpeha8CdC7ho/U2OIsB4g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-gnu@1.79.0': + resolution: {integrity: sha512-tK1E93A5LVzISg4ngpKJnfTs7EqtIUceGI7MQ4GyDjJiLi8wPCkEyKlj2xkyKWZ1yzkDJyLHTBJ5/iFWRdnJvg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-musl@1.79.0': + resolution: {integrity: sha512-qhQvUIrngXivA2A9pQ+xPCychztn/5qUv7yS3gDwXv3w7Rag+eTeeXWmRyx+t7XsW5x6LuY/8AsTq36UgFIblg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-s390x-gnu@1.79.0': + resolution: {integrity: sha512-sv6AaVgU/eE6u+6WFiQVDcPPwTxP6IJMSB9k701W2r/r6Tx465e8vPvVyRxquNH4Vy6KwRNu90mVbxXJN8+5gg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-gnu@1.79.0': + resolution: {integrity: sha512-iFZL02deziHslb3jEX9KdqlAkYoo4fGyotchKDzdfK1f5mxlIBeiQeHhvK3iFpuEJSB4ma/qeFn9oxPiwnhUPQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-musl@1.79.0': + resolution: {integrity: sha512-3DtZR2raqObnh7wXZoFYFd0Fw7skBvcb3f7A+/lkEiDuh8hrE6vv9b/62Qxao1a9/OeHLw/FcXlXzgsW9wTRFg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxlint/binding-openharmony-arm64@1.79.0': + resolution: {integrity: sha512-Oatt4GuA1WJkqzk2ozx4HrWROOi7opV3AKDw/U8qDIqeTqzsjn5K2x3REJMNjU3/KU/Bkq96Zi3CknaiDTaC/Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxlint/binding-win32-arm64-msvc@1.79.0': + resolution: {integrity: sha512-NAgZr9Qp8nIA9rpo0JEvwiabTF/2UVqBNnupBG9X4kxXcQoScJUTi+qHhvabb9s/thgj5wQ4XcIaJvb+ZMgoKw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxlint/binding-win32-ia32-msvc@1.79.0': + resolution: {integrity: sha512-+KyXjIvcpaXmWW/j9NNY5yWjrIVxaX18VyIheQy3jwc2GSYgpCr7MGI/HxIGQ/shAL5IWEKbhsqoMpAO5Stiog==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxlint/binding-win32-x64-msvc@1.79.0': + resolution: {integrity: sha512-mEelcCMMBS57sIXh2veGMNy+pQwuGtcMxHxGIZWQ5Ba9pJ5jCCUFOZB9E2JhBaxGsURe+WGe0zJp4RVre52gpQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxlint/plugins@1.79.0': + resolution: {integrity: sha512-S0uyoxakDINJ4DPgqxGlEEvrdSMeQb7Z2lKVjxoY2gwsbZbfg2Xr8Klfeo5ZeraHmmdBCELFUHkSe6KEmBpMvg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@playwright/test@1.63.0': + resolution: {integrity: sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==} + engines: {node: '>=20'} + hasBin: true + + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + + '@prisma/adapter-pg@7.10.0': + resolution: {integrity: sha512-N7nwSor0HO1Kz6xBv0TPAjAPysKK0fac6p4fVN3ensLOuzc/83Fgmln5k92eK/cvzqdkSR/2kkAqlbcdwVrwpw==} + + '@prisma/client-runtime-utils@7.10.0': + resolution: {integrity: sha512-cnCy7lUV8/CctgKVEmqAbSLAmwqJdE/qAlqTBk/0NDk59zEb2cZ0M0M0E4vVPnqbSEYudRroQDvOWfUZH6RIfw==} + + '@prisma/client@7.10.0': + resolution: {integrity: sha512-Ubw/QS9JGIBSBUsyxAUQuK/Jcu0Tsva7le7QbLd91Kix9yJvYDdj5QkwgEbbZniH80dd+sziQcALPc+HnvQC8Q==} + engines: {node: ^20.19 || ^22.12 || >=24.0} + peerDependencies: + prisma: '*' + typescript: '>=5.4.0' + peerDependenciesMeta: + prisma: + optional: true + typescript: + optional: true + + '@prisma/config@7.10.0': + resolution: {integrity: sha512-Rcg828gIRE3HOQ3pOATFjV5d/P0U9OIobxhd/IMxlfWjA4vru0eGwb0AIwFw0rmcLMVShohZYWPixVxkBHsxUA==} + + '@prisma/debug@7.10.0': + resolution: {integrity: sha512-caygJKtltmRIgdJ3jRpkOr7yM4DW6zxo5uOmojKWFb3asnxWoRkQOwZmXBgD8FZp4htrX+nMpcWqDwzlQ1+Y4g==} + + '@prisma/debug@7.2.0': + resolution: {integrity: sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==} + + '@prisma/dev@0.24.17': + resolution: {integrity: sha512-UvdZzmpFwknnfreh6Jije84ekkYGPYEJhXG1tFzCsCfQyzJifrOo38eZc0qajzvaC6OLUOrN9ML5XfCnEZL9DA==} + + '@prisma/driver-adapter-utils@7.10.0': + resolution: {integrity: sha512-u8zkcRLlaryO652T4qavBg0HmzNW5tSKdsCn6hc1PhWAp/J6k0vrxLuUs+b9o+HcjsK7Dfa01o4OFSn0frauJA==} + + '@prisma/engines-version@7.10.0-4.0edf323efd1d98336f3f0a68684b56f689b900d3': + resolution: {integrity: sha512-8OJ6RuZTZ06eFUOtBwxVmv8XMmOW6HWN5F+uxUbZkGxR0Bfab1dfAdXaHPmR5mb59E+fmUo8IOzXlbLY1SClbw==} + + '@prisma/engines@7.10.0': + resolution: {integrity: sha512-KNumN6NHFwybvfdYzTee9pqwx5PvknpWAaHn6L5NsbrKdl+SQrsVZs9opKs6U6SAsvB26HDt3WybRjOhgoWOYQ==} + + '@prisma/fetch-engine@7.10.0': + resolution: {integrity: sha512-Zqyu8DY14t6W/xwmAxUYWCXtHrvQnSvT644EAZSsdM8NSmCS74vJJbBKdVsK3ucFpnUWkEpbO1a0CxJXrg130g==} + + '@prisma/get-platform@7.10.0': + resolution: {integrity: sha512-0bra1LFYi8xNw0yqV62bHJNQk4BKleOngZiqPWIQc7a3+9q6rqsnqJ15BuepP8943PFMvtmgnP52juZsyYkA6w==} + + '@prisma/get-platform@7.2.0': + resolution: {integrity: sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==} + + '@prisma/query-plan-executor@7.2.0': + resolution: {integrity: sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ==} + + '@prisma/streams-local@0.1.11': + resolution: {integrity: sha512-0TcebL559MByKqTJ+SsrFIEg228iw8UCVRFckzgfRSiJqczhs+MuAgWOF9lnOIV/IVqvu+KMnFTH0eDeTQMpUg==} + engines: {bun: '>=1.2.0', node: '>=22.0.0'} + + '@prisma/studio-core@0.33.0': + resolution: {integrity: sha512-V2fX/nKEymNTrHXwfP26PGjoLStO35Ogu+ex7CFJbLrMYEcZxxZpiSNOs7px23Hk5mzLWvM5RsqG6Ka+rha+wg==} + engines: {node: ^20.19 || ^22.12 || >=24.0, pnpm: '8'} + peerDependencies: + '@types/react': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.5': + resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} + + '@protobufjs/eventemitter@1.1.1': + resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==} + + '@protobufjs/fetch@1.1.1': + resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.2': + resolution: {integrity: sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.2': + resolution: {integrity: sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==} + + '@radix-ui/number@1.1.3': + resolution: {integrity: sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==} + + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + + '@radix-ui/primitive@1.1.7': + resolution: {integrity: sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==} + + '@radix-ui/react-accessible-icon@1.1.15': + resolution: {integrity: sha512-WTQwcAvQf5sOcuUyi90lKPbhwcvQ+j55cjrSmeaN+L2vKU3DooOvlKw2MDeiJ5IkV5N905KW0/fGojKOBhD11A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-accordion@1.2.20': + resolution: {integrity: sha512-jDhG9FvAEnlhnjrsINbNXcUa4G+L1KqSkJSunkbKEzFRcAb52jvM0PjPxPRvhe1HNc5F5yc0yzzWeeqlH4yBIg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-alert-dialog@1.1.23': + resolution: {integrity: sha512-VAYOiQRqj3GPpYJE0I9J+X8Ip05cyVlNdKOFeiGS2Ou1HHGfpl0BxOyZm6nmVDyU+W+NF3/XLzmjHmVGydhwgA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-arrow@1.1.15': + resolution: {integrity: sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-aspect-ratio@1.1.15': + resolution: {integrity: sha512-fy+dyVR+90nelK8rqIznFlxzx7uPcGbhxH8Nfr2bHb4UfSe+e3hklOC0luK0hDwVwnRX7xTRySpsrQVeW+/oNQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-avatar@1.2.6': + resolution: {integrity: sha512-4ULOTJ/mqy2hT9GlWa/MFHxHSvH3nJzHnZM1waNsc5Bonv7i70aNenghXmD97S6OJ81ekXONGGt4nT1r0PfEdA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-checkbox@1.3.11': + resolution: {integrity: sha512-Gnptr9pDDQxD3hgq2dtPbtrp/c2qH1mBwIzw3X/ivrMb2e1t0jMTi606fVEqFPaQR1ggXIVQWKj3P2WW9v7zGQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collapsible@1.1.20': + resolution: {integrity: sha512-mcGesGplBnzN2sbvJETzpCNfSMyPnb29q1GRLU+Ib7bJrpIG2ywmRoh2V5VbA2uNvKikKUlVbAPks7JDjz4A8Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.1.15': + resolution: {integrity: sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-compose-refs@1.1.5': + resolution: {integrity: sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context-menu@2.3.7': + resolution: {integrity: sha512-CtXP35dxaB5T3zXSd+E3uHe/QpXcpYnZmxp6OaIbfthtfW4wyb77M23BG+bwIJDtsMwEP/YssdsmNyZu7jhWew==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-context@1.2.2': + resolution: {integrity: sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.23': + resolution: {integrity: sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-direction@1.1.4': + resolution: {integrity: sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.19': + resolution: {integrity: sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dropdown-menu@2.1.24': + resolution: {integrity: sha512-geq8l2rJkxvkXsT9RMgtUE3P8pITFpTsvYpbySi1IH4fZEABD/Gp85myayFgxk0ktljGMJnCbeFkyTusvSvv7g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.6': + resolution: {integrity: sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.16': + resolution: {integrity: sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-form@0.1.16': + resolution: {integrity: sha512-Q4TLEn2A7TAypxwmd6R9EwrlXDvkfYSDMrq9/887AXAGh+G1rH+kYJKSTv+Si9Y0JPKTwKYv6PviAJosysNimA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-hover-card@1.1.23': + resolution: {integrity: sha512-H8qONfZd3ltrU3+jHCIgITbWo6e1iTKvP9DHdrvYbX48ooRM5FjEDTn16AMwdfuOGkWdZEhpl3PLL/Wk/AnHDQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.4': + resolution: {integrity: sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-label@2.1.15': + resolution: {integrity: sha512-o/rdYEwZTTo5tjknnPeyQFU45kUC4i/XyeDPP+HGyi6XqpOP6Zf5Ya5vh/Yfe9Id5JiuWnnAx2XqIeD3UYZt0g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-menu@2.1.24': + resolution: {integrity: sha512-uW7RVuU6Lp/ZtfeY4b3kL32zccgEWvPv1+cf17ubYzHa9cL8AHokmk36cG/XEiH/smbQvumnieXX9j/e9RqJWA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-menubar@1.1.24': + resolution: {integrity: sha512-eeVs0vf7cuqXaM0qLQCPcufImiJNVBXdJDLu7ZGYl2732UH23Qat/foNGrr6vYV3/DdTsBqASoggUFgH14OcZA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-navigation-menu@1.2.22': + resolution: {integrity: sha512-ou7iLEJ+yrhQndkkA4U21XIdS/CS45F4iXIkTZcb6/Ne9EMsOuDudVmCwmDnfFZZ+y1FZqXRNSIgBy+YMvZVZg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-one-time-password-field@0.1.16': + resolution: {integrity: sha512-Tj9P6ntAJEw52oq/F0AGknXR4XncxEt7XU47O3xJQOiWfLzEy3d9gtgKfvjSzGxzHkfL+VzvxGu2KTFsloJqXw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-password-toggle-field@0.1.11': + resolution: {integrity: sha512-4gvFnmDXu3dgj21CqsufzIameRvlRd4SBqaWhcrlrNhRo0Y5i/49AmRJYe1fdAM3G2VNBbmin4b0D6cdQocwgw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popover@1.1.23': + resolution: {integrity: sha512-mw58MrBlyHWFisTOYignD0vf/3gdcgAR+9of1s9G/38CbFiUwH1nCDkc0AUM9IrXFgN5Ue8n45j9WCgyM1sbiQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.3.7': + resolution: {integrity: sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.17': + resolution: {integrity: sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.10': + resolution: {integrity: sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.10': + resolution: {integrity: sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-progress@1.1.16': + resolution: {integrity: sha512-5XnomAsoZZCY+KNTxbIghpGqPruZvKFNlvcAljVAOdDRDsH4/OZQxhtwo5wdtoDM5R6MhJBb2sPnDuRFep3lzg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-radio-group@1.4.7': + resolution: {integrity: sha512-cgYFEkntCxppHZgtSZ+7vh0wbZQ+IC7PPMw8DSnRG27B6kDd32/Zw0OJt7dGDigCoprMuWHjg2PvUn3PYvPFoQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.19': + resolution: {integrity: sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-scroll-area@1.2.18': + resolution: {integrity: sha512-Zn5Cd171wxsO3Dfg8HaW6RifTb9CYTKQJHs/G4+LN1GfmJpaQMZQyQxMprVPHpaz7QY4l9BxK2JwQuzHsXC8nA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-select@2.3.7': + resolution: {integrity: sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-separator@1.1.15': + resolution: {integrity: sha512-jOLO4lssEzWpoDu7G+Ze4VjwMRUBt291pnZD0gmalREZipnTX3wadQo7Fy48GCTfe14/YRN6rw/rOJqrE85Wxw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slider@1.4.7': + resolution: {integrity: sha512-mTSLf1GC/C0moWjTbvCM6Qn/gBjvlFt1azuWF2v7MN5C3Zq2U2J2lN3ZEYkpujuOU5Ro7A28wkviSxaKnG0BYg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-slot@1.3.3': + resolution: {integrity: sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-switch@1.3.7': + resolution: {integrity: sha512-48tB/4dn2UVLBCYhTu9AuR63IHl73l/qLbLgxd86noTUor4/K4LFDAcYjK+isP5313qxaFpjPVogE7+Y0/V3Kw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tabs@1.1.21': + resolution: {integrity: sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toast@1.2.23': + resolution: {integrity: sha512-ofhyAsYaocRGOs/n0XWdUOSVzEAG6BfrMVM8z0c0kLEWY38w/0WuMFPTJP/HVaZPYkMvHZoKIIhNcjbTCBILPg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toggle-group@1.1.19': + resolution: {integrity: sha512-OtnwuSVjd1Ofi+AdnvhsjQdyuhCDwYs1w9RyB5BN/OavXOVQo42SYqQjwUnbPnaiPFBpQ9aX70dWeee+v2oBLA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toggle@1.1.10': + resolution: {integrity: sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toggle@1.1.18': + resolution: {integrity: sha512-7lonPlKfSacd20GlOBx2ltuVKz9oqWYZz+oMQyOltw6t1y2nyftj2ZmwwUHYn49kqfDWcp8dNZm5NgV+5Z+mug==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toolbar@1.1.19': + resolution: {integrity: sha512-Ph0IvtYw4VB12ZnZg+YtrGs8yJQsnizwo/zu0R4Y/nWugtJzA7Pg1eWeuDR9+LSqn+xjamss+UOSOJJJ4gx8jw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tooltip@1.2.16': + resolution: {integrity: sha512-6EamKFRRnlpdadndbZ6LMwycfwkwPte1B42hs6QA0gYhjaOKqW4PZ4pjaW9UrlDX5eVt/OjncE7BFTPL5nmZhg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.4': + resolution: {integrity: sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.6': + resolution: {integrity: sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.5': + resolution: {integrity: sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.5': + resolution: {integrity: sha512-ge3ipobwSXTj4JyVtswQ7qZj0ZHdtbGuOno/LrgAAeSxtsJ6Vs4Gz5IkPH2bmqpjcLUFoqGhA/mueuIf63UXlA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-is-hydrated@0.1.3': + resolution: {integrity: sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.4': + resolution: {integrity: sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.4': + resolution: {integrity: sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.4': + resolution: {integrity: sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.4': + resolution: {integrity: sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.2.11': + resolution: {integrity: sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.3': + resolution: {integrity: sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==} + + '@redis/bloom@6.2.1': + resolution: {integrity: sha512-huQgNLaCIZfQ9SeLn4q9124uOUd8HbZDYHwwUzNcRgHqCHiHKl2dDxMqJCeWh8cMqZAoWuHR8XnWbDMIf+o7ag==} + engines: {node: '>= 20.0.0'} + peerDependencies: + '@redis/client': ^6.2.1 + + '@redis/client@6.2.1': + resolution: {integrity: sha512-LzxBY7SIBvvJiyCgcaJZZakE3fJrZZ++i24+EDW9fKpCl68D35uJcKFpZZwCfOoG9WZTbyZlMzMeM0gtOAMU9Q==} + engines: {node: '>= 20.0.0'} + peerDependencies: + '@node-rs/xxhash': ^1.1.0 + '@opentelemetry/api': '>=1 <2' + peerDependenciesMeta: + '@node-rs/xxhash': + optional: true + '@opentelemetry/api': + optional: true + + '@redis/json@6.2.1': + resolution: {integrity: sha512-AFIUJ8Gj0DaaSBHYuSt8+O0oYWM+50OK1c0OmodB7XERIA8+BbyV3O4v76f9iccWasd1/7qjfZTpuzexUaZtrQ==} + engines: {node: '>= 20.0.0'} + peerDependencies: + '@redis/client': ^6.2.1 + + '@redis/search@6.2.1': + resolution: {integrity: sha512-2vfOAOyYFE7UUw3sBBlkqqruBtOUS4HRY5MtW4hp83llrwvtrTE4r22CEqXddlV+54zkLxBE4nmsIJ/dpezQrQ==} + engines: {node: '>= 20.0.0'} + peerDependencies: + '@redis/client': ^6.2.1 + + '@redis/time-series@6.2.1': + resolution: {integrity: sha512-kiYniph04dJOole+L359B6C9E+jYS2uDP7hca6Onj0xF38ZIpyxARO0Iq0W4ZRn1e8Q6vqW00QFZVSMRA/2Ijw==} + engines: {node: '>= 20.0.0'} + peerDependencies: + '@redis/client': ^6.2.1 + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@sideway/address@4.1.5': + resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} + + '@sideway/formula@3.0.1': + resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} + + '@sideway/pinpoint@2.0.0': + resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@tailwindcss/node@4.3.3': + resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==} + + '@tailwindcss/oxide-android-arm64@4.3.3': + resolution: {integrity: sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.3.3': + resolution: {integrity: sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.3.3': + resolution: {integrity: sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.3.3': + resolution: {integrity: sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': + resolution: {integrity: sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': + resolution: {integrity: sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-arm64-musl@4.3.3': + resolution: {integrity: sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-linux-x64-gnu@4.3.3': + resolution: {integrity: sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-x64-musl@4.3.3': + resolution: {integrity: sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-wasm32-wasi@4.3.3': + resolution: {integrity: sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': + resolution: {integrity: sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.3.3': + resolution: {integrity: sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.3.3': + resolution: {integrity: sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==} + engines: {node: '>= 20'} + + '@tailwindcss/vite@4.3.3': + resolution: {integrity: sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 || ^8 + + '@tanstack/history@1.162.1': + resolution: {integrity: sha512-DR9t6lfLVdrjgCwpglrR9DR7Ok8/HlXjcOE+goWXF3zyuLUO/ug7vMbSFxTqrQTtbRghJfyhmIZ0S6LhPIy44w==} + engines: {node: '>=20.19'} + + '@tanstack/query-core@5.102.8': + resolution: {integrity: sha512-ZNjkJ33CqvPNec/6lZBnHqLc3EVGPZ9ySLhYahU9TcuRFdmwXewuj0c4hwSWcGHqEUwcSrKeZ+oGcvPBqXcQcg==} + + '@tanstack/react-query@5.102.8': + resolution: {integrity: sha512-TYBea4OuXWD7MhaSHq069TWbFe7rcwWN6kzT7JF0OKi1K6c1gTv2IzD6A6ExJsCMozdkqBWeuIUZmu4KQg0O5A==} + peerDependencies: + react: ^18 || ^19 + + '@tanstack/react-router@1.170.32': + resolution: {integrity: sha512-SIpxvaTKco100a5ZR3ePmArbhtm3XOx+w1dpGYY9gxHDta4iXSKDdQuhLonwJbIMkVJsU1rwXf0UDHMrF/1snw==} + engines: {node: '>=20.19'} + peerDependencies: + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + + '@tanstack/react-store@0.9.3': + resolution: {integrity: sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/router-core@1.171.27': + resolution: {integrity: sha512-wDwSLvoLwIaNcnx9UNcN9Mb7Y8QwCYq1U1RQZwyN186gnkIoIYI2SOxy8VqH1vFigbkHkk4FmwMAQlghPgDK2g==} + engines: {node: '>=20.19'} + + '@tanstack/store@0.9.3': + resolution: {integrity: sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==} + + '@testcontainers/postgresql@12.1.0': + resolution: {integrity: sha512-Pjf2VSVNirEPfz36nidyrVAnZvc2YhajOznY4VgyEsvfTd5qiMNOuPq96drREvxAUtXl5SFLX7vXj7sSq4aTcA==} + + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/user-event@14.6.7': + resolution: {integrity: sha512-MPCpX8bxe8zS+JmmTwLp8jd0dy1rAm60Te/SL8JrQM3qvQJcBOs1d7IefJMyZzqM3EWBrDn/LWDt1BCGu4ASfg==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/d3-array@3.0.3': + resolution: {integrity: sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==} + + '@types/d3-color@3.1.0': + resolution: {integrity: sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==} + + '@types/d3-delaunay@6.0.1': + resolution: {integrity: sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==} + + '@types/d3-format@3.0.1': + resolution: {integrity: sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==} + + '@types/d3-geo@3.1.0': + resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} + + '@types/d3-interpolate@3.0.1': + resolution: {integrity: sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-scale@4.0.2': + resolution: {integrity: sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==} + + '@types/d3-shape@3.1.7': + resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==} + + '@types/d3-time-format@2.1.0': + resolution: {integrity: sha512-/myT3I7EwlukNOX2xVdMzb8FRgNzRMpsZddwst9Ld/VFe6LyJyRp0s32l/V9XoUzk+Gqu56F/oGk6507+8BxrA==} + + '@types/d3-time@3.0.0': + resolution: {integrity: sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/docker-modem@3.0.6': + resolution: {integrity: sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==} + + '@types/dockerode@4.0.1': + resolution: {integrity: sha512-cmUpB+dPN955PxBEuXE3f6lKO1hHiIGYJA46IVF3BJpNsZGvtBDcRnlrHYHtOH/B6vtDOyl2kZ2ShAu3mgc27Q==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/express-http-proxy@1.6.7': + resolution: {integrity: sha512-CEp9pbnwVI1RzN9PXc+KESMxwUW5r1O7tkWb5h7Wg/YAIf+KulD/zKev8fbbn+Ljt0Yvs8MXwV2W6Id+cKxe2Q==} + + '@types/express-serve-static-core@5.1.3': + resolution: {integrity: sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==} + + '@types/express@5.0.6': + resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} + + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + + '@types/lodash@4.17.25': + resolution: {integrity: sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==} + + '@types/long@4.0.2': + resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} + + '@types/node@18.19.130': + resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} + + '@types/node@24.13.3': + resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==} + + '@types/pg@8.23.1': + resolution: {integrity: sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A==} + + '@types/qs@6.15.1': + resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/react-dom@19.2.7': + resolution: {integrity: sha512-I8bPpDLcHBv1qiIiXDCy71Rt8eQDKJP0sMSWJphDdAcdqiJ1sGpZamavoEIRZmYzjia9LuEb2HlYdDpmoENpvQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.18': + resolution: {integrity: sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==} + + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} + + '@types/serve-static@2.2.0': + resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} + + '@types/ssh2-streams@0.1.13': + resolution: {integrity: sha512-faHyY3brO9oLEA0QlcO8N2wT7R0+1sHWZvQ+y3rMLwdY1ZyS1z0W3t65j9PqT4HmQ6ALzNe7RZlNuCNE0wBSWA==} + + '@types/ssh2@0.5.52': + resolution: {integrity: sha512-lbLLlXxdCZOSJMCInKH2+9V/77ET2J6NPQHpFI0kda61Dd1KglJs+fPQBchizmzYSOJBgdTajhPqBO1xxLywvg==} + + '@types/ssh2@1.15.6': + resolution: {integrity: sha512-oGdxhBqcRTwSTKFm+9EiKzkNVYRLEFkcW44lhguvBalGJbWfGnDt/ezwSUZc+SF9m9bMc3VyklNAtp7zICjS5w==} + + '@typescript/typescript-aix-ppc64@7.0.2': + resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [aix] + + '@typescript/typescript-darwin-arm64@7.0.2': + resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] + + '@typescript/typescript-darwin-x64@7.0.2': + resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] + + '@typescript/typescript-freebsd-arm64@7.0.2': + resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [freebsd] + + '@typescript/typescript-freebsd-x64@7.0.2': + resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [freebsd] + + '@typescript/typescript-linux-arm64@7.0.2': + resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + + '@typescript/typescript-linux-arm@7.0.2': + resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} + engines: {node: '>=16.20.0'} + cpu: [arm] + os: [linux] + + '@typescript/typescript-linux-loong64@7.0.2': + resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} + engines: {node: '>=16.20.0'} + cpu: [loong64] + os: [linux] + + '@typescript/typescript-linux-mips64el@7.0.2': + resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} + engines: {node: '>=16.20.0'} + cpu: [mips64el] + os: [linux] + + '@typescript/typescript-linux-ppc64@7.0.2': + resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [linux] + + '@typescript/typescript-linux-riscv64@7.0.2': + resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} + engines: {node: '>=16.20.0'} + cpu: [riscv64] + os: [linux] + + '@typescript/typescript-linux-s390x@7.0.2': + resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} + engines: {node: '>=16.20.0'} + cpu: [s390x] + os: [linux] + + '@typescript/typescript-linux-x64@7.0.2': + resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + + '@typescript/typescript-netbsd-arm64@7.0.2': + resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [netbsd] + + '@typescript/typescript-netbsd-x64@7.0.2': + resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [netbsd] + + '@typescript/typescript-openbsd-arm64@7.0.2': + resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [openbsd] + + '@typescript/typescript-openbsd-x64@7.0.2': + resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [openbsd] + + '@typescript/typescript-sunos-x64@7.0.2': + resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [sunos] + + '@typescript/typescript-win32-arm64@7.0.2': + resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] + + '@typescript/typescript-win32-x64@7.0.2': + resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + + '@visx/curve@4.0.1-alpha.0': + resolution: {integrity: sha512-jRu61Uz274pV1zyioXmboyrLutYbnKsgjj4njSGCnhdXj5GkZvZbg+ThDb6oOzoAnJOBRLz4rzPlWvNJOzuVMg==} + + '@visx/event@4.0.1-alpha.0': + resolution: {integrity: sha512-EQqCMSv/s8NbFjo+hz3FKsvvYfP+2QslsFJ/24/O5l/W+7UC6J6aAvO0ujVwrTwdYbuQ+vhxKi1xdPdKR/qj1g==} + + '@visx/grid@4.0.1-alpha.0': + resolution: {integrity: sha512-rycutGmTHO+znNdPumheWMglm7YfpffvRwUkVy5zy4WoORIuKTMkDxwnOzHG2xMxU3EE/YCd37xFV5AxA30yeg==} + peerDependencies: + react: ^16.14.0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0 + + '@visx/group@4.0.1-alpha.0': + resolution: {integrity: sha512-V19l7iQ7jccBv8kao/EByuI6o4xtxzzLV9nqVI1hRvmdzTVsuLpqlwzYCZUXJaTVvUWf8s4D2SQFjGkj/Nw+0w==} + peerDependencies: + react: ^16.14.0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0 + + '@visx/point@4.0.1-alpha.0': + resolution: {integrity: sha512-ijTfr/Nx09f03vIj9nyTr3z4Xth4Y75427UaogJh6dnIRLMEFHQOwNu791sbfiNj0a+ZXuaE32h0vKrFe4/8Qg==} + + '@visx/responsive@4.0.1-alpha.0': + resolution: {integrity: sha512-o+1zGywQZY0+yOx3Iw87wc4bbPJRr/HnIukTwfOz4UVyj9pB1OQNVHB7OORO1+LBHJceWpB31co/ZV9KHncKrA==} + peerDependencies: + react: ^16.14.0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0 + + '@visx/scale@4.0.1-alpha.0': + resolution: {integrity: sha512-nzjeE87vFSAXGWFiiNfBpNLAf0Q8Qmf6syvKLjqNi4kGZkdhbUll3E/59YsgWXmjM8+llPLWzGsP+JPvo5eq1A==} + + '@visx/shape@4.0.1-alpha.0': + resolution: {integrity: sha512-62QeiVNmPlterQGwhkEDcbq7M0MqY0lBsK5QKXtM9ZoPZWkuGV3aykA3+Xu20B2FAvyJq4LqJzBc7Sxr+EAdbA==} + peerDependencies: + react: ^16.14.0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0 + + '@visx/vendor@4.0.0-alpha.0': + resolution: {integrity: sha512-6I+MuqXBcv9jnlcVowHoHKSdk9gXTWkHLKyqBwRWg7LY6A3Ei8SHfubpqGV5rBUSppxMq2RszPJUS6w+H0YgmQ==} + + '@vitejs/plugin-react@6.1.1': + resolution: {integrity: sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + oxc-transform-react: ^0.145.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true + oxc-transform-react: + optional: true + + '@vitest/browser-preview@4.1.11': + resolution: {integrity: sha512-iPKSE6Ibayey6HFgK1V1/aHgyhx7HSRk1YMi+lnBZGmlIiNV5Uc7xRkD9Su8RDylTxDECK23t7kTHdRKoqSYDQ==} + peerDependencies: + vitest: 4.1.11 + + '@vitest/browser@4.1.11': + resolution: {integrity: sha512-bwMovvAeuTFOK5kIFevw4VEf+1gVEICv4SYK4k3knJOxl6b1zEWud8mYKD73e1B0odAn174h1MofURy2TPWf3w==} + peerDependencies: + vitest: 4.1.11 + + '@vitest/expect@4.1.11': + resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==} + + '@vitest/mocker@4.1.11': + resolution: {integrity: sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.11': + resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==} + + '@vitest/runner@4.1.11': + resolution: {integrity: sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==} + + '@vitest/snapshot@4.1.11': + resolution: {integrity: sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==} + + '@vitest/spy@4.1.11': + resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==} + + '@vitest/utils@4.1.11': + resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==} + + '@voidzero-dev/vite-plus-core@0.3.0': + resolution: {integrity: sha512-aOqoqIWaF+Q/geDU48pC2rVFEVSvLV1GGj/NdvhUiBhCZntoFNbwI+hjUeG8BMaPG67sOV6ey+/sgkdmGmKqaw==} + engines: {node: ^20.19.0 || ^22.18.0 || >=24.11.0} + peerDependencies: + '@arethetypeswrong/core': ^0.18.1 + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.4.0 || ^0.5.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + publint: ^0.3.8 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + typescript: ^5.0.0 || ^6.0.0 || ^7.0.0 + unplugin-unused: ^0.5.0 + unrun: '*' + yaml: ^2.4.2 + peerDependenciesMeta: + '@arethetypeswrong/core': + optional: true + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + publint: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + typescript: + optional: true + unplugin-unused: + optional: true + unrun: + optional: true + yaml: + optional: true + + '@voidzero-dev/vite-plus-darwin-arm64@0.3.0': + resolution: {integrity: sha512-9ADr1egZ8T4tJOqrpQLhoDl95Y74R95+bsvjmin0gy1C0eQVhpmcNnBfb07KFNhJioJp9MMO7F7Dx4fQL5SKsw==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [darwin] + + '@voidzero-dev/vite-plus-darwin-x64@0.3.0': + resolution: {integrity: sha512-GegasVCwNeDOkNyvhLOuwU1+T2JkjY/Tq+SOvwphUpVcqQ6OOAUq9LlpoXviO2QL/Kq2NbMYjiAfPKVSTLUFQw==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [darwin] + + '@voidzero-dev/vite-plus-linux-arm64-gnu@0.3.0': + resolution: {integrity: sha512-nYI3KNYXkXjRPsSdR4Lr7J2xMxfR1+TplWlG/dV37qVXWAjbyHpoAlbULjZBAVJMyXRNlcADhBrEwXe4g6s48A==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@voidzero-dev/vite-plus-linux-arm64-musl@0.3.0': + resolution: {integrity: sha512-HRlVA3AOcuGXmOdHhQ+Zv5XAaKbYF9si5rRHoOsKl0UyBo4txA3OoJfmP0WjanfLUNmu85JyO2dO1ptL4C6wgg==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@voidzero-dev/vite-plus-linux-x64-gnu@0.3.0': + resolution: {integrity: sha512-9A+dFScPfwcrzF/rRR0zH8++2hOf6xtFmN/5LyzyfUywtw9MILXcC72IMcOeL6QRJwKUMsudi1rFeDE59azNvw==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@voidzero-dev/vite-plus-linux-x64-musl@0.3.0': + resolution: {integrity: sha512-KfIV3qaPdaOOE8JQMRHRE34FtZocl9O86XLTP6JMjDUlcx8FPgf8/fz/HFqJ8g232vM+JsgLI/YTVeXP8LkTKw==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@voidzero-dev/vite-plus-win32-arm64-msvc@0.3.0': + resolution: {integrity: sha512-KRhdy5K13AYx9KBfCVHRrK7zSZU+bMW9CL6gTai+UkJgAmDJi1kjdSNboZOjO8mrzUnTCrELgMI2tnstcxSTuA==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [win32] + + '@voidzero-dev/vite-plus-win32-x64-msvc@0.3.0': + resolution: {integrity: sha512-7+G+GxGmxdpQO0zjiGnkZFXKGqm0CrVduebRsJd6ccuOuxCQYPxLcoHq4WOaGrh56SrAGS7XjhnQCrXRkzKUVQ==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [win32] + + '@whatwg-node/promise-helpers@1.3.2': + resolution: {integrity: sha512-Nst5JdK47VIl9UcGwtv2Rcgyn5lWtZ0/mhRQ4G8NN2isxpq2TO30iqHzmwoJycjWuyUfg3GFXqP/gFHXeV57IA==} + engines: {node: '>=16.0.0'} + + '@yuku-codegen/binding-darwin-arm64@0.5.48': + resolution: {integrity: sha512-yo96Oef12WzqnphInfz/eexVse3+kWgfGS5g2S3rFS3dcGn1ENW9xLFDZUP9rh+yP76DOq38wBoFi1+I9+6qBg==} + cpu: [arm64] + os: [darwin] + + '@yuku-codegen/binding-darwin-x64@0.5.48': + resolution: {integrity: sha512-aRCTw0EZC4bVosmw//0OMYP5tGWFE0Cu5yUBFkUbhXx/iBzvORcJ2xPNlOp/vtCCo9Ys4vp8b0DigJV6uOVb2g==} + cpu: [x64] + os: [darwin] + + '@yuku-codegen/binding-freebsd-x64@0.5.48': + resolution: {integrity: sha512-CA0AQAEApDkbw51PdLWMtKPJ41/7rvXsS3SJs+phG7fHJI+MuFzWuLbkucZfZoEOiDscmcsfYIdgL8BsfuyKKQ==} + cpu: [x64] + os: [freebsd] + + '@yuku-codegen/binding-linux-arm-gnu@0.5.48': + resolution: {integrity: sha512-DuSQlk8bH4gpmW3/00P0NLagAcMv8jOxjT40cQmxKRkktr+SUOALCfkT89tdDq3qtY95NR2GXOZ7AjNh7KKqCw==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@yuku-codegen/binding-linux-arm-musl@0.5.48': + resolution: {integrity: sha512-bxj4Ee+wlaJcWJwft2ReJXWw5sfl1qavDz6+dlRdU1xfTEtjPSNiAWhiCHnJR0R4Ygd57DnzSQmAVGvFv6RcGw==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@yuku-codegen/binding-linux-arm64-gnu@0.5.48': + resolution: {integrity: sha512-mk5JVWh+0JOe5ue8k17kbYX8uGBoKt3ZqoCyxNh4nYAAcX7+X1tFUiU7jbjctu4vHeejCBFSTdQ021+V31cUCQ==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@yuku-codegen/binding-linux-arm64-musl@0.5.48': + resolution: {integrity: sha512-4q3vkrNghbllyxOm2KesFLxCPKHF7r3JyQ7BWZccY1j2Y05yKoIFhoWCqIuQ2W/dpte9RI0+OVfwyxnrKg6fkA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@yuku-codegen/binding-linux-x64-gnu@0.5.48': + resolution: {integrity: sha512-csd4M1EVrGaohM8acM6gq1zpUA/Rwe2ulUMBKUcwQXm/k6n7cq1A++qdew78SOVb4do3JH1WE+WFwoGQAcWc1w==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@yuku-codegen/binding-linux-x64-musl@0.5.48': + resolution: {integrity: sha512-KcDuEOT+GFoVKdvAWOv1v9iYjwnmvMZlO+j1Rw+5PYdeFLGWGzv/DD11y4SAAdwXIFcil4T0hibeIaF82WStMg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@yuku-codegen/binding-win32-arm64@0.5.48': + resolution: {integrity: sha512-HI8qNrI8dWM5BuqIMKsqornRvTNFrE6sm5zToIJ9YIa9zt5+29P7fJ7Nr39EVf6dAWSb6q7JSpScJnRsQ+FgZA==} + cpu: [arm64] + os: [win32] + + '@yuku-codegen/binding-win32-x64@0.5.48': + resolution: {integrity: sha512-X5YWJLO6EfBZpeBqO0AYESnUizbpFDWArcvVD61w0PEWQ3CaFRLnbQXs+kpM4ZZfGMfIE22zfA08QSY67q7TNQ==} + cpu: [x64] + os: [win32] + + '@yuku-parser/binding-darwin-arm64@0.5.48': + resolution: {integrity: sha512-If8mb7HH3vqghJ2NNZ8SuHfhsnjVzOxJpB8xcNOXS5WjYrs2mUhHIh5KOIvK13hDOzh0htGeGK3A6MsiEqE7HQ==} + cpu: [arm64] + os: [darwin] + + '@yuku-parser/binding-darwin-x64@0.5.48': + resolution: {integrity: sha512-EimvPXfspzxf1K11eB6tCW5oiQEXB8g84T2wP1TwzQagdDKo33bkmmVF0B32vTIpXnk/Ifu5IB61izZ1MylljA==} + cpu: [x64] + os: [darwin] + + '@yuku-parser/binding-freebsd-x64@0.5.48': + resolution: {integrity: sha512-0GcUMrumLHheThY9r5Tp46gaZYzn0irWPS1Zba6WY+vVQfhUtzGiWgXxI6tuXX0N32kEaaEVRpkKctvo6Kx3aQ==} + cpu: [x64] + os: [freebsd] + + '@yuku-parser/binding-linux-arm-gnu@0.5.48': + resolution: {integrity: sha512-8S5T5wjCC73dmmpQeZ49aYsSunIUM3D4Fc6rdK96c+Ayg/p3FmeSPF3xuLZHejcTmqJIIvnbfPlUF+rB6DITjQ==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@yuku-parser/binding-linux-arm-musl@0.5.48': + resolution: {integrity: sha512-tTmbxvnUHcK2/crS9547vk2SMmsajH1yqJ8ltXhIuHJgqR1v+d9n9KT+kSayo/5CS76LegeYxhMFjEivBH2hFA==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@yuku-parser/binding-linux-arm64-gnu@0.5.48': + resolution: {integrity: sha512-KGYCBMqI2zfwyhgq5tpPVNe7jpUeYTBm8DhjdS+zqWNumde/PEC170QE5RHxcOAlsirIDeIUk0jqx+r/axoFSw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@yuku-parser/binding-linux-arm64-musl@0.5.48': + resolution: {integrity: sha512-2wTSMsCSXLTc2lZUjMAuU5X4cje55u205WJqfV5NWNF6j9pW/tXyxr15dJeekj8ziLqBXzIsj4DbRh4sY/WcjA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@yuku-parser/binding-linux-x64-gnu@0.5.48': + resolution: {integrity: sha512-d/6v9UnGglVu1WC2JQyv/5aWSi5fXZeGSlidCfmHp4+N65N1GDKUnFtys5MK5eAPeAjTgSHGGtOc/yCcKTlv3A==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@yuku-parser/binding-linux-x64-musl@0.5.48': + resolution: {integrity: sha512-gX19gw6u4ApPy7SYMPKfFlEkrtj6WlORvrTKK3sBQqjyV+8+mUAkQgxXNjHw4RnOiAmVYg7TOlZcg8d+Qqod9A==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@yuku-parser/binding-win32-arm64@0.5.48': + resolution: {integrity: sha512-w6cQQLbqj3Jcom5Q7ifm103NUOQ9d+Cb4VU5lkrZDjMnwVJ9Hzzg1vCQR7miJuF44vhCXldbme5UryE3giEKlA==} + cpu: [arm64] + os: [win32] + + '@yuku-parser/binding-win32-x64@0.5.48': + resolution: {integrity: sha512-4gO0HmG7fzFxrw1rs0dUdnnaY9YgennjETqDWrTSp7x9fmTUOAoN4VsMfP7YyliQeG1WJJHc55O+rOhmsLppow==} + cpu: [x64] + os: [win32] + + '@yuku-toolchain/types@0.5.43': + resolution: {integrity: sha512-kSpvPntnXw5+lYjO71ffBEnQ5ycQ74KGIYknh0TS4xeyCuBkOqxyJumxZkMhLBBUCLjDAbx2+Icnr3Zh4ftjpQ==} + + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + acorn@8.18.0: + resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + archiver-utils@5.0.2: + resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==} + engines: {node: '>= 14'} + + archiver@7.0.1: + resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==} + engines: {node: '>= 14'} + + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + array.prototype.reduce@1.0.8: + resolution: {integrity: sha512-DwuEqgXFBwbmZSRqt3BpQigWNUoqw9Ml2dTWdF3B2zQlQX4OeUE0zyuzX0fX0IbTvjdkZbcBTU3idgpO78qkTw==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + asn1@0.2.6: + resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + async-lock@1.4.1: + resolution: {integrity: sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==} + + async-retry@1.3.3: + resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + auto-bind@5.0.1: + resolution: {integrity: sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + aws-ssl-profiles@1.1.2: + resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} + engines: {node: '>= 6.0.0'} + + axios@1.20.0: + resolution: {integrity: sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==} + + b4a@1.8.1: + resolution: {integrity: sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==} + peerDependencies: + react-native-b4a: '*' + peerDependenciesMeta: + react-native-b4a: + optional: true + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + bare-events@2.9.2: + resolution: {integrity: sha512-AIPKioV7/Y/8KfZ3AAhjPJxLLbY49S64Ym5DakZlUg75qQiTgUq9hEJoEwa4eUezPUlXRy/i5NpsKvo9jgKmoA==} + peerDependencies: + bare-abort-controller: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + + bare-fs@4.8.1: + resolution: {integrity: sha512-N1nnXdHZAOSstz0XiHikGS4HGMH4CnSwhqWdGQQMqqdvp4Jybm9sE3R1WVnpWVd4SFkc8ryPDBLViNLwiEqECg==} + engines: {bare: '>=1.28.0'} + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + + bare-path@3.1.2: + resolution: {integrity: sha512-ZyKbsuuqK6Ag0K8pX6V5Txq6XeJRvY+wXucnFGRjiyVYP9YWDpIQugk/b+enRYrEYBJaqLzghRQpXPMR7341Nw==} + + bare-stream@2.13.4: + resolution: {integrity: sha512-PcrQ8lVLbiJscNm1Kez+Yp4Gy4AHGcN1lzwjvf5NybWen7VvEgUfyfnXYJ2zNqWnzOfCb1Abq6lH8ti0syQszA==} + peerDependencies: + bare-abort-controller: '*' + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + bare-buffer: + optional: true + bare-events: + optional: true + + bare-url@2.5.4: + resolution: {integrity: sha512-Gxa7UVWBr0/edU1b+TJhn/AZvMQUj9OGspvYsaTYQrAbZA4BOTZGL3LiZxvD+CeMlDH4juwD84+eTAp/bLYW5g==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + base64url@3.0.1: + resolution: {integrity: sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==} + engines: {node: '>=6.0.0'} + + bcrypt-pbkdf@1.0.2: + resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} + + better-result@2.10.0: + resolution: {integrity: sha512-oQhh0y1qo2/ZKdAAEvHZAqKKiHOFU5k/bW96fE2ScgQOVkJRiHwB+nOS1SgFsYqRlxMDWvefXi9Q3px7QvgNDw==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + body-parser@2.3.0: + resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} + engines: {node: '>=18'} + + brace-expansion@2.1.4: + resolution: {integrity: sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==} + + brace-expansion@5.0.9: + resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} + engines: {node: 20 || >=22} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + buffer-crc32@1.0.0: + resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} + engines: {node: '>=8.0.0'} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + buildcheck@0.0.7: + resolution: {integrity: sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==} + engines: {node: '>=10.0.0'} + + byline@5.0.0: + resolution: {integrity: sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q==} + engines: {node: '>=0.10.0'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + c12@3.3.4: + resolution: {integrity: sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==} + peerDependencies: + magicast: '*' + peerDependenciesMeta: + magicast: + optional: true + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + change-case-all@2.1.0: + resolution: {integrity: sha512-v6b0WWWkZUMHVuYk82l+WROgkUm4qEN2w5hKRNWtEOYwWqUGoi8C6xH0l1RLF1EoWqDFK6MFclmN3od6ws3/uw==} + + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + classnames@2.5.1: + resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone@2.1.2: + resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} + engines: {node: '>=0.8'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + engines: {node: '>=0.10.0'} + + cn@0.2.5: + resolution: {integrity: sha512-OCjZtMeQfXbI4Es1+EIjkd77gvWzaE689gD8KhfexlqjClC06qR1MQBR+Z35ZMSPNEBWyHiItW1Soy0UvwNv9w==} + engines: {node: '>=20'} + hasBin: true + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + common-tags@1.8.2: + resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} + engines: {node: '>=4.0.0'} + + compress-commons@6.0.2: + resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==} + engines: {node: '>= 14'} + + confbox@0.2.4: + resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} + + confbox@0.3.1: + resolution: {integrity: sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA==} + + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + content-type@2.1.0: + resolution: {integrity: sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==} + engines: {node: '>=18'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-es@3.1.1: + resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + cpu-features@0.0.10: + resolution: {integrity: sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==} + engines: {node: '>=10.0.0'} + + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + + crc32-stream@6.0.0: + resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==} + engines: {node: '>= 14'} + + cross-inspect@1.0.1: + resolution: {integrity: sha512-Pcw1JTvZLSJH83iiGWt6fRcT+BjZlCDRVwYLbUcHzv/CRpB7r0MlSrGbIyQvVSNyGnbt7G4AXuyCiDR3POvZ1A==} + engines: {node: '>=16.0.0'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + d3-array@3.2.1: + resolution: {integrity: sha512-gUY/qeHq/yNqqoCKNq4vtpFLdoCdvyNpWoC/KNjhGbhDuQpAM9sIQQKkXSNpXa9h5KySs/gzm7R88WkUutgwWQ==} + engines: {node: '>=12'} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.2: + resolution: {integrity: sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==} + engines: {node: '>=12'} + + d3-format@3.1.0: + resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} + engines: {node: '>=12'} + + d3-geo@3.1.0: + resolution: {integrity: sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + date-fns@4.4.0: + resolution: {integrity: sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==} + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deepmerge-ts@8.0.0: + resolution: {integrity: sha512-ICNjaP0ML+eSdEpJYQC46XiAn/UjAdwbEl0dE8p85ZTeNDinN4Kd4+9jS4OSAuH7st6eC7rQhsqTF5zIDaUm2g==} + engines: {node: '>=16.0.0'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + + delaunator@5.1.0: + resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + depd@1.1.2: + resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} + engines: {node: '>= 0.6'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + dependency-graph@1.0.0: + resolution: {integrity: sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==} + engines: {node: '>=4'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + docker-compose@1.4.2: + resolution: {integrity: sha512-rPHigTKGaEHpkUmfd69QgaOp+Os5vGJwG/Ry8lcr8W/382AmI+z/D7qoa9BybKIkqNppaIbs8RYeHSevdQjWww==} + engines: {node: '>= 6.0.0'} + + docker-modem@5.0.7: + resolution: {integrity: sha512-XJgGhoR/CLpqshm4d3L7rzH6t8NgDFUIIpztYlLHIApeJjMZKYJMz2zxPsYxnejq5h3ELYSw/RBsi3t5h7gNTA==} + engines: {node: '>= 8.0'} + + dockerode@5.0.1: + resolution: {integrity: sha512-avsq/xk4YPIrn0CgleX5bjT9Y8IT1p9PxrNQ++RBQ2WEyFfHCTDsT9kmyxz+H/axnjAwg8wJWEIuPGOUuNupiA==} + engines: {node: '>= 14.17'} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dotenv@17.4.2: + resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} + engines: {node: '>=12'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + effect@3.20.0: + resolution: {integrity: sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw==} + + elkjs@0.11.1: + resolution: {integrity: sha512-zxxR9k+rx5ktMwT/FwyLdPCrq7xN6e4VGGHH8hA01vVYKjTFik7nHOxBnAYtrgYUB1RpAiLvA1/U2YraWxyKKg==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + empathic@2.0.0: + resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} + engines: {node: '>=14'} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + enhanced-resolve@5.24.5: + resolution: {integrity: sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==} + engines: {node: '>=10.13.0'} + + env-paths@3.0.0: + resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + es-abstract-get@1.0.0: + resolution: {integrity: sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==} + engines: {node: '>= 0.4'} + + es-abstract@1.24.2: + resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} + engines: {node: '>= 0.4'} + + es-array-method-boxes-properly@1.0.0: + resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@2.3.2: + resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.4: + resolution: {integrity: sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==} + engines: {node: '>= 0.4'} + + es6-promise@4.2.8: + resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==} + + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + events-universal@1.0.1: + resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + + express-http-proxy@2.1.2: + resolution: {integrity: sha512-FXcAcs7Nf/hF73Mzh0WDWPwaOlsEUL/fCHW3L4wU6DH79dypsaxmbnAildCLniFs7HQuuvoiR6bjNVUvGuTb5g==} + engines: {node: '>=6.0.0'} + + express-openid-connect@3.4.0: + resolution: {integrity: sha512-5EXkpyZM44QToEyS/zQ0tVTFZ6WRsYVqkXrPfMkkeLKU3GHcnM7rgR3gW5OsS2cDlbSq4IVud3aCMillwlsYmw==} + engines: {node: ^20.19.0 || ^22.12.0 || >= 23.0.0} + peerDependencies: + express: '>= 4.17.0' + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + + exsolve@1.1.1: + resolution: {integrity: sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==} + + fast-check@3.23.2: + resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} + engines: {node: '>=8.0.0'} + + fast-decode-uri-component@1.0.1: + resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-querystring@1.1.2: + resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} + + fast-uri@3.1.7: + resolution: {integrity: sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==} + + fastq@1.20.3: + resolution: {integrity: sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + + find-my-way@9.7.0: + resolution: {integrity: sha512-f2JHn75x2JlwUwLenZypgczR7YWMb/uO9BvUXtus+JMgkbIkLADd38cI4EiV+OQqrGo1Zlq6V8wnqMJ8e62wUQ==} + engines: {node: '>=20'} + + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} + engines: {node: '>= 6'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.2.0: + resolution: {integrity: sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + generate-function@2.3.1: + resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} + + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + + get-port-please@3.2.0: + resolution: {integrity: sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==} + + get-port@5.1.1: + resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} + engines: {node: '>=8'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + giget@3.3.1: + resolution: {integrity: sha512-r+mvuDjrjMpsdw46Kmeydb8bdHm7wOKw8wNBtTndkjbPjgAp5oUJUxRE76wZFknxIPokfWvep2qSXK37aXE6zg==} + hasBin: true + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + grammex@3.1.13: + resolution: {integrity: sha512-LnPnhOBLEJEVKS8WFDVaA397L9Kq55Q9oSITJiVLHVdhAclfUkWzQv74KhvZHKL2Q09Pb1XdsrOsZ4LfTFFTEg==} + + graphmatch@1.1.1: + resolution: {integrity: sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg==} + + graphql-tag@2.12.7: + resolution: {integrity: sha512-xnE/NFzy+0eIesvAsREJZ284zTl/wYuBAvpsFSDhRGRdRHdnE90M21Q3xAWyYInb0J756c6x0pIQ62+vtvOs1Q==} + engines: {node: '>=10'} + peerDependencies: + graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + graphql@16.14.2: + resolution: {integrity: sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + http-errors@1.8.1: + resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} + engines: {node: '>= 0.6'} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + iconv-lite@0.7.3: + resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + immutable@5.1.9: + resolution: {integrity: sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==} + + import-from@4.0.0: + resolution: {integrity: sha512-P9J71vT5nLlDeV8FHs5nNxaLbrpfAV5cF5srvbZfpwpcJoM/xZR3hiv+q+SAnuSmuGbXMWud063iIMx/V/EWZQ==} + engines: {node: '>=12.2'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + + invariant@2.2.4: + resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-absolute@1.0.0: + resolution: {integrity: sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==} + engines: {node: '>=0.10.0'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-document.all@1.0.0: + resolution: {integrity: sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + is-property@1.0.2: + resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-relative@1.0.0: + resolution: {integrity: sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==} + engines: {node: '>=0.10.0'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-unc-path@1.0.0: + resolution: {integrity: sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==} + engines: {node: '>=0.10.0'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isbot@5.2.2: + resolution: {integrity: sha512-iQcBXcd+Rv/pkubRyGh2utW2j1oPG5hZY6TUhVPpqK4G+o3IbxpJNx04hgksjc/N7GK5pEorUxDeg31cFgEk/w==} + engines: {node: '>=18'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + + joi@17.13.7: + resolution: {integrity: sha512-MF80Dm5Y2veNy8QWVx9Bj3ui4mo7+VPSPsR1M+oaHXV0Gx6zGX9a2F+OZG3Blby9tOlzU9Rs5FUimlEhbKtfnQ==} + + jose@6.2.11: + resolution: {integrity: sha512-A5NPn7g8EAzGU3IzRs+Yiq8K5n3ypYS75M5+KKiVHdUexfpWK1kP4ZMq7QnTGDoMj6TJ1dtcEJjW60yZDXS4hg==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + + lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + loglevel@1.9.2: + resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} + engines: {node: '>= 0.6.0'} + + long@4.0.0: + resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} + engines: {node: 20 || >=22} + + lru.min@1.1.5: + resolution: {integrity: sha512-5J9ysMYUpYIg9RF2vJpy9SinEmSviFSe0GyPpCQ4L5QSkLAgeLXlTAOu2ZwWUU5m+0SBl6gUU1R1ZQB3aKypfA==} + engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} + + lucide-react@1.41.0: + resolution: {integrity: sha512-6lksP35l6KszDKUeRTi4LV7i6DEe0Yzl2ALJm9j4c5xEYN91GdW1xGsawGMOg2mgjF5GHBVX8pKX9kP+cWsP3Q==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + map-cache@0.2.2: + resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==} + engines: {node: '>=0.10.0'} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@1.1.1: + resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + minimatch@10.2.6: + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} + engines: {node: 18 || 20 || >=22} + + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mysql2@3.24.3: + resolution: {integrity: sha512-OKfWHkMAg9v06neq8FmSyhbxPQKABN9PAW5G9/bDTXzJBO5xXtkKL0V27vju7HQWk9UD4Od5BZsvCtBTB1CPEw==} + engines: {node: '>= 8.0'} + peerDependencies: + '@types/node': '>= 8' + + named-placeholders@1.1.6: + resolution: {integrity: sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==} + engines: {node: '>=8.0.0'} + + nan@2.28.0: + resolution: {integrity: sha512-fTsDz99OTq2sVePhGdp4qQhggZFtKr64ZNVyVajRKtMOkJxYekplBh577PiJB12v/D3s2E5cGtOI45LWp6rnLQ==} + + nanoid@3.3.18: + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + negotiator@1.1.0: + resolution: {integrity: sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==} + engines: {node: '>=18'} + + normalize-path@2.1.1: + resolution: {integrity: sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==} + engines: {node: '>=0.10.0'} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + oauth4webapi@3.8.7: + resolution: {integrity: sha512-4RxcKxXjuItDFZ20RRPf4YTw3kpeXJyCgJFxVzJ068A7PNJ18st2Dg90tlC1LkSDS0GecroagCLHYEIVUhCAkw==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.getownpropertydescriptors@2.1.9: + resolution: {integrity: sha512-mt8YM6XwsTTovI+kdZdHSxoyF2DI59up034orlC9NfweclcWOt7CVascNNLp6U+bjFVCVCIh9PwS76tDM/rH8g==} + engines: {node: '>= 0.4'} + + obug@2.1.4: + resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} + engines: {node: '>=12.20.0'} + + ohash@2.0.12: + resolution: {integrity: sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + on-headers@1.1.0: + resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + openid-client@6.8.7: + resolution: {integrity: sha512-gtKthNu7evSBvTdrrlHb4F3Fi9dcwlb5QaITlCs+9mfpvuOi0Q3qtBf5+iY4sEP8hy1qCoAdxBNPDcmZeVSDzQ==} + + own-keys@1.0.2: + resolution: {integrity: sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==} + engines: {node: '>= 0.4'} + + oxfmt@0.64.0: + resolution: {integrity: sha512-XZ4GFBN/PLbXKq+0zrgpQfPKYuJlUuj+nzZJY7UpIbFMNyefNLCdN9EwViycNqnYcv0wrn0jXcQLlqJp8RCKBg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + svelte: ^5.0.0 + vite-plus: '*' + peerDependenciesMeta: + svelte: + optional: true + vite-plus: + optional: true + + oxlint-tsgolint@7.0.2001: + resolution: {integrity: sha512-KjK/XLcXr1DSyonKhsuFqJRiuKqcyG9j3LJ8nkOsrLzGvodBPqzHOKauy10asLMDI0sUpvb+1sxlzff3udZvfg==} + hasBin: true + + oxlint@1.79.0: + resolution: {integrity: sha512-hVJ9hq9m2unPS+Of4eJJgCPdIeCC+3DHEUX3tkmrPJr3OK2hz7PhXwgC+ZP71ZcYu8cCDEtQrqLxWNvxBppBVg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=7.0.2001' + vite-plus: '*' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + vite-plus: + optional: true + + parse-filepath@1.0.2: + resolution: {integrity: sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==} + engines: {node: '>=0.8'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-root-regex@0.1.2: + resolution: {integrity: sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==} + engines: {node: '>=0.10.0'} + + path-root@0.1.1: + resolution: {integrity: sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==} + engines: {node: '>=0.10.0'} + + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + perfect-debounce@2.1.0: + resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} + + pg-cloudflare@1.4.0: + resolution: {integrity: sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==} + + pg-connection-string@2.14.0: + resolution: {integrity: sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-pool@3.14.0: + resolution: {integrity: sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==} + peerDependencies: + pg: '>=8.0' + + pg-protocol@1.16.0: + resolution: {integrity: sha512-sILXutLVjCLjcDuOmvhX5e2Z4cS5qG/6Bu3VkpFwdf/633ElGLpEh9bgmuI5I4sqKqkifQiGyiCcx1HdtrK7tg==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + + pg@8.23.0: + resolution: {integrity: sha512-Ip2EQCngowJLGOfCwkFhPXU7/ljlhn6Rxlmy4XYfL2Y+vyRM59+8uR2xqRWKdYmbXmxCFOAmKxBuSUCdF34qLg==} + engines: {node: '>= 16.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + + pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + + pkg-types@2.3.2: + resolution: {integrity: sha512-v0sVXzj7oPGysr543YYZLYbcJNJsKikSsp/fFzoxQ12ewY3ZZr7oCPC8y7OlmxfYB3QPvriXmuPD8KZggE1vqg==} + + playwright-core@1.63.0: + resolution: {integrity: sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==} + engines: {node: '>=20'} + hasBin: true + + playwright@1.63.0: + resolution: {integrity: sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==} + engines: {node: '>=20'} + hasBin: true + + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss@8.5.28: + resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} + engines: {node: ^10 || ^12 || >=14} + + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-array@3.0.4: + resolution: {integrity: sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==} + engines: {node: '>=12'} + + postgres-bytea@1.0.1: + resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} + engines: {node: '>=0.10.0'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + + postgres@3.4.7: + resolution: {integrity: sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==} + engines: {node: '>=12'} + + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + prisma@7.10.0: + resolution: {integrity: sha512-o0ornyJOWgygVAzGCpr8PdXV8EJLHyVGDDUr/voBQt8Azzw8cYTByzzPGcA/m4tCkPcnJA8raEOv2CslsKhPEw==} + engines: {node: ^20.19 || ^22.12 || >=24.0} + hasBin: true + peerDependencies: + better-sqlite3: '>=9.0.0' + typescript: '>=5.4.0' + peerDependenciesMeta: + better-sqlite3: + optional: true + typescript: + optional: true + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + + proper-lockfile@4.1.2: + resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} + + properties-reader@3.0.1: + resolution: {integrity: sha512-WPn+h9RGEExOKdu4bsF4HksG/uzd3cFq3MFtq8PsFeExPse5Ha/VOjQNyHhjboBFwGXGev6muJYTSPAOkROq2g==} + engines: {node: '>=18'} + + protobufjs@7.6.6: + resolution: {integrity: sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==} + engines: {node: '>=12.0.0'} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} + + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + + qs@6.16.0: + resolution: {integrity: sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==} + engines: {node: '>=0.6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + radix-ui@1.6.7: + resolution: {integrity: sha512-QBdhh1arIEUvPC0dQ5+nwWAxt7+N+oP/9jPwjJkGFoSk/sqxg32gJtSXGtFh8frAIcS6oC9cx2Q+7KYCQLOAeA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + range-parser@1.3.0: + resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==} + engines: {node: '>= 0.6'} + + raw-body@2.5.3: + resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} + engines: {node: '>= 0.8'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + rc9@3.1.0: + resolution: {integrity: sha512-ufjkNVzbRHKcCOmTahZkmVsyc3W+MSk3jY03m+a7tGHkIsdVMG9l10/3HvFbWkkKzY5VFp3pkRsIo/UYgmFL7Q==} + + react-dom@19.2.8: + resolution: {integrity: sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==} + peerDependencies: + react: ^19.2.8 + + react-is@17.0.1: + resolution: {integrity: sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==} + + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react@19.2.8: + resolution: {integrity: sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==} + engines: {node: '>=0.10.0'} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readable-stream@4.7.0: + resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + + readdirp@5.1.1: + resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==} + engines: {node: '>= 20.19.0'} + + redis@6.2.1: + resolution: {integrity: sha512-Z9VHtgYs48PiQC77X9O2Er8Hj4T+5BtFjT91/vi5Is1D04N72cA946ZslM1ImJw8ZctFBZWAVjM7S5wJNeHMpg==} + engines: {node: '>= 20.0.0'} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + remeda@2.33.4: + resolution: {integrity: sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==} + + remove-trailing-separator@1.1.0: + resolution: {integrity: sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + ret@0.5.0: + resolution: {integrity: sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==} + engines: {node: '>=10'} + + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + + retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + robust-predicates@3.0.3: + resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-array-concat@1.1.4: + resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} + engines: {node: '>=0.4'} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safe-regex2@5.1.1: + resolution: {integrity: sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==} + hasBin: true + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + seroval-plugins@1.6.4: + resolution: {integrity: sha512-R0f1U9hmn38+dFMz6b6ab8lwucmw4AtiY7St+JPWudy1dm+Bs3g884nyrsH9Cy6rKpZKLYayXuMda9GZ/fl8JQ==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + + seroval@1.6.4: + resolution: {integrity: sha512-LErWMNS2RRFdu2RMA5u/PA59/IWs0XsikyEXGQ2/36iEWFrdG0ABmg17E17cikrv76891kOAMq3TkTFXpwAHXw==} + engines: {node: '>=10'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + sha.js@2.4.12: + resolution: {integrity: sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==} + engines: {node: '>= 0.10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + sonner@2.0.8: + resolution: {integrity: sha512-UM/ByIoFra8yzV75n1o0Puu0bw5U/9UNnDacrJNspekBewIfsQ3D6ez1nvlWpt7aTsO6rujQtifBpycwIivqlg==} + peerDependencies: + '@types/react': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + split-ca@1.0.1: + resolution: {integrity: sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + sponge-case@2.0.3: + resolution: {integrity: sha512-i4h9ZGRfxV6Xw3mpZSFOfbXjf0cQcYmssGWutgNIfFZ2VM+YIWfD71N/kjjwK6X/AAHzBr+rciEcn/L34S8TGw==} + + sql-escaper@1.5.1: + resolution: {integrity: sha512-4toX5E1fQbBrpfXidaHnF0669nkAdETeIPTs2SUjxxD7RRIs9ICG4gtpmfc68JCEKehsdwLFqBu9VlQqZ1P1gg==} + engines: {bun: '>=1.0.0', deno: '>=2.0.0', node: '>=12.0.0'} + + ssh-remote-port-forward@1.0.4: + resolution: {integrity: sha512-x0LV1eVDwjf1gmG7TTnfqIzf+3VPRz7vrNIjX6oYLbeCrf/PeVY6hkT68Mg+q02qXxQhrLjB0jfgvhevoCRmLQ==} + + ssh2@1.17.0: + resolution: {integrity: sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==} + engines: {node: '>=10.16.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + statuses@1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + std-env@4.2.0: + resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + streamx@2.28.1: + resolution: {integrity: sha512-zEzXb0s5Cds7tqMH6rhZ05lcJydCWiQPEwiNngVqzsxCc962vLY4Uw+mW7od8kDH258k2Uz/JrOkdIAAhSh9VA==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string.prototype.trim@1.2.11: + resolution: {integrity: sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.10: + resolution: {integrity: sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + swap-case@3.0.3: + resolution: {integrity: sha512-6p4op8wE9CQv7uDFzulI6YXUw4lD9n4oQierdbFThEKVWVQcbQcUjdP27W8XE7V4QnWmnq9jueSHceyyQnqQVA==} + + tailwind-merge@3.6.0: + resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==} + + tailwindcss@4.3.3: + resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==} + + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + + tar-fs@2.1.5: + resolution: {integrity: sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==} + + tar-fs@3.1.3: + resolution: {integrity: sha512-/hU4AXnIdZu+Gvl1pk0oI5f5HxWsCJRtY2aFaJdk9VvyL48DWU6iU5WAIPG+wIi1YvWA6eTJvIviP/tMAZZNwQ==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tar-stream@3.2.1: + resolution: {integrity: sha512-nqsEO8zLZJvrOMdEwkA0QdCLFbetHMn95Zqu4fKwX+hkaTWJPZZOrxx/PwtxoK0MMGQmBQNRW3CPs8IFYQz4cQ==} + + teex@1.0.1: + resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} + + temporal-polyfill@1.0.4: + resolution: {integrity: sha512-MLEU0qOD2uXlz24oINNtdLZQl8RgmMxSnRtKEGZGGetTJjlRwQJjk+VsJ4EREaUoiaTb8NJOcUiKtoAiWn9EBg==} + + temporal-spec@1.0.1: + resolution: {integrity: sha512-wxVoanmDeavXie1vu2JaQ3WIc3JZnWAOYFBsJyATaVsXsycKYUflGsyBmrRSnoCpZJpwPyr38VpgSUlQ8CbFxg==} + + temporal-utils@1.0.2: + resolution: {integrity: sha512-1B8Dl4KzrOvsNUlpoWGno2VLQlxroLjDgc5NBjVC9ax9ymdo+ezfyvhyjEMx+IVfhINr6CIG2gcnlP95iRrybQ==} + + terser@5.51.2: + resolution: {integrity: sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==} + engines: {node: '>=10'} + hasBin: true + + testcontainers@12.1.0: + resolution: {integrity: sha512-YjDLqIITuhGLMnM10yhg3oV6lIG5IMpz1R1DPBZoOOks83q7i7IVpeSWRTiyl7roozjiyLmwIoLK/KY8OnZmIA==} + engines: {node: '>= 22.22'} + + text-decoder@1.2.7: + resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.3.1: + resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinypool@2.1.0: + resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} + engines: {node: ^20.0.0 || >=22.0.0} + + tinyrainbow@3.1.1: + resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} + engines: {node: '>=14.0.0'} + + title-case@3.0.3: + resolution: {integrity: sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==} + + tmp@0.2.7: + resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==} + engines: {node: '>=14.14'} + + to-buffer@1.2.2: + resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==} + engines: {node: '>= 0.4'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsx@4.23.13: + resolution: {integrity: sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw==} + engines: {node: '>=18.0.0'} + hasBin: true + + tw-animate-css@1.4.0: + resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} + + tweetnacl@0.14.5: + resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} + + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.8: + resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} + engines: {node: '>= 0.4'} + + typescript@7.0.2: + resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} + engines: {node: '>=16.20.0'} + hasBin: true + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + unc-path-regex@0.1.2: + resolution: {integrity: sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==} + engines: {node: '>=0.10.0'} + + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + + undici@8.10.2: + resolution: {integrity: sha512-/y4/bH9YNU5hi9NIrpOuvGXFcxrj3CMrV+/AYpowAYTpHn8gX/XPFjNy766FPoYY0miQhdW977JFWKGNhBdwyQ==} + engines: {node: '>=22.19.0'} + + unixify@1.0.0: + resolution: {integrity: sha512-6bc58dPYhCMHHuwxldQxO3RRNZ4eCogZ/st++0+fcC1nr0jiGUtAdBJ2qzmLQWSxbtz42pWt4QQMiZ9HvZf5cg==} + engines: {node: '>=0.10.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + url-join@4.0.1: + resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} + + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + util-promisify@3.0.0: + resolution: {integrity: sha512-uWRZJMjSWt/A1J1exfqz7xiKx2kVpAHR5qIDr6WwwBMQHDoKbo2I1kQN62iA2uXHxOSVpZRDvbm8do+4ijfkNA==} + + valibot@1.4.2: + resolution: {integrity: sha512-gjdCvJ6d3RyHAneqxMYMW9QMCwYMb3jpOO0IyHZV1bnRHFBHrX3VkIILt5XYR0WhwHiH7Mty8ovuPZ/O3gamrg==} + peerDependencies: + typescript: '>=5' + peerDependenciesMeta: + typescript: + optional: true + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vite-plus@0.3.0: + resolution: {integrity: sha512-GNWbWuWD37frCSFrz6MLzUo62bTv5IOJozHEgZYOkxsLkuQtTwm4TowzpfoGrSsfwhAAtfPd/sK1Y0+v1SwhZA==} + engines: {node: ^20.19.0 || ^22.18.0 || >=24.11.0} + hasBin: true + peerDependencies: + '@vitest/browser-playwright': 4.1.11 + '@vitest/browser-webdriverio': 4.1.11 + peerDependenciesMeta: + '@vitest/browser-playwright': + optional: true + '@vitest/browser-webdriverio': + optional: true + + vitest@4.1.11: + resolution: {integrity: sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.11 + '@vitest/browser-preview': 4.1.11 + '@vitest/browser-webdriverio': 4.1.11 + '@vitest/coverage-istanbul': 4.1.11 + '@vitest/coverage-v8': 4.1.11 + '@vitest/ui': 4.1.11 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.22: + resolution: {integrity: sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.21.3: + resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.3: + resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==} + engines: {node: '>=12'} + + yuku-codegen@0.5.48: + resolution: {integrity: sha512-p7HxD5Xl4jzDzqMrGePAOeSHmRY4g58h4HuGq15weQFPxuPWd/W6e7nqp/+Lea6JfpOdBwJOAyXFqIZ/J9Zfnw==} + + yuku-parser@0.5.48: + resolution: {integrity: sha512-OWBfhrpgK9+/4+IXG9oT8Bao4AhViQA7vdyNNH7EUg8dQYgwa70XtIBWTpCEme1P1ECyoDNYkn0wT63f8XRcVA==} + + zeptomatch@2.1.0: + resolution: {integrity: sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==} + + zip-stream@6.0.1: + resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} + engines: {node: '>= 14'} + +snapshots: + + '@apollo/cache-control-types@1.0.3(graphql@16.14.2)': + dependencies: + graphql: 16.14.2 + + '@apollo/protobufjs@1.2.8': + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.5 + '@protobufjs/eventemitter': 1.1.1 + '@protobufjs/fetch': 1.1.1 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.2 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.2 + '@types/long': 4.0.2 + long: 4.0.0 + + '@apollo/server-gateway-interface@2.0.0(graphql@16.14.2)': + dependencies: + '@apollo/usage-reporting-protobuf': 4.1.2 + '@apollo/utils.fetcher': 3.1.0 + '@apollo/utils.keyvaluecache': 4.0.0 + '@apollo/utils.logger': 3.0.0 + graphql: 16.14.2 + + '@apollo/server@5.5.1(graphql@16.14.2)(supports-color@8.1.1)': + dependencies: + '@apollo/cache-control-types': 1.0.3(graphql@16.14.2) + '@apollo/server-gateway-interface': 2.0.0(graphql@16.14.2) + '@apollo/usage-reporting-protobuf': 4.1.2 + '@apollo/utils.createhash': 3.0.1 + '@apollo/utils.fetcher': 3.1.0 + '@apollo/utils.isnodelike': 3.0.0 + '@apollo/utils.keyvaluecache': 4.0.0 + '@apollo/utils.logger': 3.0.0 + '@apollo/utils.usagereporting': 2.1.0(graphql@16.14.2) + '@apollo/utils.withrequired': 3.0.0 + '@graphql-tools/schema': 10.1.0(graphql@16.14.2) + async-retry: 1.3.3 + body-parser: 2.3.0(supports-color@8.1.1) + content-type: 1.0.5 + cors: 2.8.6 + finalhandler: 2.1.1(supports-color@8.1.1) + graphql: 16.14.2 + loglevel: 1.9.2 + lru-cache: 11.5.2 + negotiator: 1.1.0 + whatwg-mimetype: 4.0.0 + transitivePeerDependencies: + - supports-color + + '@apollo/usage-reporting-protobuf@4.1.2': + dependencies: + '@apollo/protobufjs': 1.2.8 + + '@apollo/utils.createhash@3.0.1': + dependencies: + '@apollo/utils.isnodelike': 3.0.0 + sha.js: 2.4.12 + + '@apollo/utils.dropunuseddefinitions@2.0.1(graphql@16.14.2)': + dependencies: + graphql: 16.14.2 + + '@apollo/utils.fetcher@3.1.0': {} + + '@apollo/utils.isnodelike@3.0.0': {} + + '@apollo/utils.keyvaluecache@4.0.0': + dependencies: + '@apollo/utils.logger': 3.0.0 + lru-cache: 11.5.2 + + '@apollo/utils.logger@3.0.0': {} + + '@apollo/utils.printwithreducedwhitespace@2.0.1(graphql@16.14.2)': + dependencies: + graphql: 16.14.2 + + '@apollo/utils.removealiases@2.0.1(graphql@16.14.2)': + dependencies: + graphql: 16.14.2 + + '@apollo/utils.sortast@2.0.1(graphql@16.14.2)': + dependencies: + graphql: 16.14.2 + lodash.sortby: 4.7.0 + + '@apollo/utils.stripsensitiveliterals@2.0.1(graphql@16.14.2)': + dependencies: + graphql: 16.14.2 + + '@apollo/utils.usagereporting@2.1.0(graphql@16.14.2)': + dependencies: + '@apollo/usage-reporting-protobuf': 4.1.2 + '@apollo/utils.dropunuseddefinitions': 2.0.1(graphql@16.14.2) + '@apollo/utils.printwithreducedwhitespace': 2.0.1(graphql@16.14.2) + '@apollo/utils.removealiases': 2.0.1(graphql@16.14.2) + '@apollo/utils.sortast': 2.0.1(graphql@16.14.2) + '@apollo/utils.stripsensitiveliterals': 2.0.1(graphql@16.14.2) + graphql: 16.14.2 + + '@apollo/utils.withrequired@3.0.0': {} + + '@ardatan/relay-compiler@13.0.2(graphql@16.14.2)': + dependencies: + '@babel/runtime': 8.0.0 + graphql: 16.14.2 + immutable: 5.1.9 + invariant: 2.2.4 + + '@as-integrations/express5@1.1.2(@apollo/server@5.5.1(graphql@16.14.2)(supports-color@8.1.1))(express@5.2.1(supports-color@8.1.1))': + dependencies: + '@apollo/server': 5.5.1(graphql@16.14.2)(supports-color@8.1.1) + express: 5.2.1(supports-color@8.1.1) + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/runtime@7.29.7': {} + + '@babel/runtime@8.0.0': {} + + '@balena/dockerignore@1.0.2': {} + + '@blazediff/core@1.9.1': {} + + '@electric-sql/pglite-socket@0.1.3(@electric-sql/pglite@0.4.3)': + dependencies: + '@electric-sql/pglite': 0.4.3 + + '@electric-sql/pglite-tools@0.3.3(@electric-sql/pglite@0.4.3)': + dependencies: + '@electric-sql/pglite': 0.4.3 + + '@electric-sql/pglite@0.4.3': {} + + '@esbuild/aix-ppc64@0.28.2': + optional: true + + '@esbuild/android-arm64@0.28.2': + optional: true + + '@esbuild/android-arm@0.28.2': + optional: true + + '@esbuild/android-x64@0.28.2': + optional: true + + '@esbuild/darwin-arm64@0.28.2': + optional: true + + '@esbuild/darwin-x64@0.28.2': + optional: true + + '@esbuild/freebsd-arm64@0.28.2': + optional: true + + '@esbuild/freebsd-x64@0.28.2': + optional: true + + '@esbuild/linux-arm64@0.28.2': + optional: true + + '@esbuild/linux-arm@0.28.2': + optional: true + + '@esbuild/linux-ia32@0.28.2': + optional: true + + '@esbuild/linux-loong64@0.28.2': + optional: true + + '@esbuild/linux-mips64el@0.28.2': + optional: true + + '@esbuild/linux-ppc64@0.28.2': + optional: true + + '@esbuild/linux-riscv64@0.28.2': + optional: true + + '@esbuild/linux-s390x@0.28.2': + optional: true + + '@esbuild/linux-x64@0.28.2': + optional: true + + '@esbuild/netbsd-arm64@0.28.2': + optional: true + + '@esbuild/netbsd-x64@0.28.2': + optional: true + + '@esbuild/openbsd-arm64@0.28.2': + optional: true + + '@esbuild/openbsd-x64@0.28.2': + optional: true + + '@esbuild/openharmony-arm64@0.28.2': + optional: true + + '@esbuild/sunos-x64@0.28.2': + optional: true + + '@esbuild/win32-arm64@0.28.2': + optional: true + + '@esbuild/win32-ia32@0.28.2': + optional: true + + '@esbuild/win32-x64@0.28.2': + optional: true + + '@floating-ui/core@1.8.0': + dependencies: + '@floating-ui/utils': 0.2.12 + + '@floating-ui/dom@1.8.0': + dependencies: + '@floating-ui/core': 1.8.0 + '@floating-ui/utils': 0.2.12 + + '@floating-ui/react-dom@2.1.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@floating-ui/dom': 1.8.0 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@floating-ui/utils@0.2.12': {} + + '@full-ui/headless-calendar@7.1.0(temporal-polyfill@1.0.4)': + dependencies: + temporal-polyfill: 1.0.4 + + '@fullcalendar/core@7.1.0(@full-ui/headless-calendar@7.1.0(temporal-polyfill@1.0.4))(temporal-polyfill@1.0.4)': + dependencies: + '@full-ui/headless-calendar': 7.1.0(temporal-polyfill@1.0.4) + temporal-polyfill: 1.0.4 + + '@fullcalendar/react@7.1.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(temporal-polyfill@1.0.4)': + dependencies: + '@full-ui/headless-calendar': 7.1.0(temporal-polyfill@1.0.4) + '@fullcalendar/core': 7.1.0(@full-ui/headless-calendar@7.1.0(temporal-polyfill@1.0.4))(temporal-polyfill@1.0.4) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + temporal-polyfill: 1.0.4 + + '@graphql-codegen/core@6.2.0(graphql@16.14.2)': + dependencies: + '@graphql-codegen/plugin-helpers': 7.3.0(graphql@16.14.2) + '@graphql-tools/schema': 10.1.0(graphql@16.14.2) + '@graphql-tools/utils': 11.2.2(graphql@16.14.2) + graphql: 16.14.2 + tslib: 2.8.1 + + '@graphql-codegen/plugin-helpers@7.3.0(graphql@16.14.2)': + dependencies: + '@graphql-tools/utils': 11.2.2(graphql@16.14.2) + change-case-all: 2.1.0 + common-tags: 1.8.2 + graphql: 16.14.2 + import-from: 4.0.0 + tslib: 2.8.1 + + '@graphql-codegen/schema-ast@6.1.0(graphql@16.14.2)': + dependencies: + '@graphql-codegen/plugin-helpers': 7.3.0(graphql@16.14.2) + '@graphql-tools/utils': 11.2.2(graphql@16.14.2) + graphql: 16.14.2 + tslib: 2.8.1 + + '@graphql-codegen/typed-document-node@7.1.0(graphql@16.14.2)': + dependencies: + '@graphql-codegen/plugin-helpers': 7.3.0(graphql@16.14.2) + '@graphql-codegen/visitor-plugin-common': 7.2.5(graphql@16.14.2) + auto-bind: 5.0.1 + change-case-all: 2.1.0 + graphql: 16.14.2 + tslib: 2.8.1 + + '@graphql-codegen/typescript-operations@6.1.6(graphql@16.14.2)': + dependencies: + '@graphql-codegen/plugin-helpers': 7.3.0(graphql@16.14.2) + '@graphql-codegen/schema-ast': 6.1.0(graphql@16.14.2) + '@graphql-codegen/visitor-plugin-common': 7.2.5(graphql@16.14.2) + auto-bind: 5.0.1 + graphql: 16.14.2 + tslib: 2.8.1 + + '@graphql-codegen/typescript-resolvers@6.1.0(graphql@16.14.2)': + dependencies: + '@graphql-codegen/plugin-helpers': 7.3.0(graphql@16.14.2) + '@graphql-codegen/typescript': 6.1.0(graphql@16.14.2) + '@graphql-codegen/visitor-plugin-common': 7.2.5(graphql@16.14.2) + '@graphql-tools/utils': 11.2.2(graphql@16.14.2) + auto-bind: 5.0.1 + graphql: 16.14.2 + tslib: 2.8.1 + + '@graphql-codegen/typescript@6.1.0(graphql@16.14.2)': + dependencies: + '@graphql-codegen/plugin-helpers': 7.3.0(graphql@16.14.2) + '@graphql-codegen/schema-ast': 6.1.0(graphql@16.14.2) + '@graphql-codegen/visitor-plugin-common': 7.2.5(graphql@16.14.2) + auto-bind: 5.0.1 + graphql: 16.14.2 + tslib: 2.8.1 + + '@graphql-codegen/visitor-plugin-common@7.2.5(graphql@16.14.2)': + dependencies: + '@graphql-codegen/plugin-helpers': 7.3.0(graphql@16.14.2) + '@graphql-tools/optimize': 2.0.0(graphql@16.14.2) + '@graphql-tools/relay-operation-optimizer': 7.1.9(graphql@16.14.2) + '@graphql-tools/utils': 11.2.2(graphql@16.14.2) + auto-bind: 5.0.1 + change-case-all: 2.1.0 + dependency-graph: 1.0.0 + graphql: 16.14.2 + graphql-tag: 2.12.7(graphql@16.14.2) + parse-filepath: 1.0.2 + tslib: 2.8.1 + + '@graphql-tools/load-files@7.0.1(graphql@16.14.2)': + dependencies: + globby: 11.1.0 + graphql: 16.14.2 + tslib: 2.8.1 + unixify: 1.0.0 + + '@graphql-tools/merge@9.2.3(graphql@16.14.2)': + dependencies: + '@graphql-tools/utils': 12.0.0(graphql@16.14.2) + graphql: 16.14.2 + tslib: 2.8.1 + + '@graphql-tools/optimize@2.0.0(graphql@16.14.2)': + dependencies: + graphql: 16.14.2 + tslib: 2.8.1 + + '@graphql-tools/relay-operation-optimizer@7.1.9(graphql@16.14.2)': + dependencies: + '@ardatan/relay-compiler': 13.0.2(graphql@16.14.2) + '@graphql-tools/utils': 12.0.0(graphql@16.14.2) + graphql: 16.14.2 + tslib: 2.8.1 + + '@graphql-tools/schema@10.1.0(graphql@16.14.2)': + dependencies: + '@graphql-tools/merge': 9.2.3(graphql@16.14.2) + '@graphql-tools/utils': 12.0.0(graphql@16.14.2) + graphql: 16.14.2 + tslib: 2.8.1 + + '@graphql-tools/utils@11.2.2(graphql@16.14.2)': + dependencies: + '@graphql-typed-document-node/core': 3.2.0(graphql@16.14.2) + '@whatwg-node/promise-helpers': 1.3.2 + cross-inspect: 1.0.1 + graphql: 16.14.2 + tslib: 2.8.1 + + '@graphql-tools/utils@12.0.0(graphql@16.14.2)': + dependencies: + '@graphql-typed-document-node/core': 3.2.0(graphql@16.14.2) + '@whatwg-node/promise-helpers': 1.3.2 + cross-inspect: 1.0.1 + graphql: 16.14.2 + tslib: 2.8.1 + + '@graphql-typed-document-node/core@3.2.0(graphql@16.14.2)': + dependencies: + graphql: 16.14.2 + + '@grpc/grpc-js@1.14.4': + dependencies: + '@grpc/proto-loader': 0.8.1 + '@js-sdsl/ordered-map': 4.4.2 + + '@grpc/proto-loader@0.7.15': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.2 + protobufjs: 7.6.6 + yargs: 17.7.3 + + '@grpc/proto-loader@0.8.1': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.2 + protobufjs: 7.6.6 + yargs: 17.7.3 + + '@hapi/hoek@9.3.0': {} + + '@hapi/topo@5.1.0': + dependencies: + '@hapi/hoek': 9.3.0 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/source-map@0.3.11': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + optional: true + + '@jridgewell/sourcemap-codec@1.6.0': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.6.0 + + '@js-sdsl/ordered-map@4.4.2': {} + + '@kwsites/file-exists@1.1.1(supports-color@8.1.1)': + dependencies: + debug: 4.4.3(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.3 + + '@oxc-project/runtime@0.146.0': {} + + '@oxc-project/types@0.146.0': {} + + '@oxfmt/binding-android-arm-eabi@0.64.0': + optional: true + + '@oxfmt/binding-android-arm64@0.64.0': + optional: true + + '@oxfmt/binding-darwin-arm64@0.64.0': + optional: true + + '@oxfmt/binding-darwin-x64@0.64.0': + optional: true + + '@oxfmt/binding-freebsd-x64@0.64.0': + optional: true + + '@oxfmt/binding-linux-arm-gnueabihf@0.64.0': + optional: true + + '@oxfmt/binding-linux-arm-musleabihf@0.64.0': + optional: true + + '@oxfmt/binding-linux-arm64-gnu@0.64.0': + optional: true + + '@oxfmt/binding-linux-arm64-musl@0.64.0': + optional: true + + '@oxfmt/binding-linux-ppc64-gnu@0.64.0': + optional: true + + '@oxfmt/binding-linux-riscv64-gnu@0.64.0': + optional: true + + '@oxfmt/binding-linux-riscv64-musl@0.64.0': + optional: true + + '@oxfmt/binding-linux-s390x-gnu@0.64.0': + optional: true + + '@oxfmt/binding-linux-x64-gnu@0.64.0': + optional: true + + '@oxfmt/binding-linux-x64-musl@0.64.0': + optional: true + + '@oxfmt/binding-openharmony-arm64@0.64.0': + optional: true + + '@oxfmt/binding-win32-arm64-msvc@0.64.0': + optional: true + + '@oxfmt/binding-win32-ia32-msvc@0.64.0': + optional: true + + '@oxfmt/binding-win32-x64-msvc@0.64.0': + optional: true + + '@oxlint-tsgolint/darwin-arm64@7.0.2001': + optional: true + + '@oxlint-tsgolint/darwin-x64@7.0.2001': + optional: true + + '@oxlint-tsgolint/linux-arm64@7.0.2001': + optional: true + + '@oxlint-tsgolint/linux-x64@7.0.2001': + optional: true + + '@oxlint-tsgolint/win32-arm64@7.0.2001': + optional: true + + '@oxlint-tsgolint/win32-x64@7.0.2001': + optional: true + + '@oxlint/binding-android-arm-eabi@1.79.0': + optional: true + + '@oxlint/binding-android-arm64@1.79.0': + optional: true + + '@oxlint/binding-darwin-arm64@1.79.0': + optional: true + + '@oxlint/binding-darwin-x64@1.79.0': + optional: true + + '@oxlint/binding-freebsd-x64@1.79.0': + optional: true + + '@oxlint/binding-linux-arm-gnueabihf@1.79.0': + optional: true + + '@oxlint/binding-linux-arm-musleabihf@1.79.0': + optional: true + + '@oxlint/binding-linux-arm64-gnu@1.79.0': + optional: true + + '@oxlint/binding-linux-arm64-musl@1.79.0': + optional: true + + '@oxlint/binding-linux-ppc64-gnu@1.79.0': + optional: true + + '@oxlint/binding-linux-riscv64-gnu@1.79.0': + optional: true + + '@oxlint/binding-linux-riscv64-musl@1.79.0': + optional: true + + '@oxlint/binding-linux-s390x-gnu@1.79.0': + optional: true + + '@oxlint/binding-linux-x64-gnu@1.79.0': + optional: true + + '@oxlint/binding-linux-x64-musl@1.79.0': + optional: true + + '@oxlint/binding-openharmony-arm64@1.79.0': + optional: true + + '@oxlint/binding-win32-arm64-msvc@1.79.0': + optional: true + + '@oxlint/binding-win32-ia32-msvc@1.79.0': + optional: true + + '@oxlint/binding-win32-x64-msvc@1.79.0': + optional: true + + '@oxlint/plugins@1.79.0': {} + + '@playwright/test@1.63.0': + dependencies: + playwright: 1.63.0 + + '@polka/url@1.0.0-next.29': {} + + '@prisma/adapter-pg@7.10.0': + dependencies: + '@prisma/driver-adapter-utils': 7.10.0 + '@types/pg': 8.23.1 + pg: 8.23.0 + postgres-array: 3.0.4 + transitivePeerDependencies: + - pg-native + + '@prisma/client-runtime-utils@7.10.0': {} + + '@prisma/client@7.10.0(prisma@7.10.0(@types/node@24.13.3)(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2))(typescript@7.0.2)': + dependencies: + '@prisma/client-runtime-utils': 7.10.0 + optionalDependencies: + prisma: 7.10.0(@types/node@24.13.3)(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2) + typescript: 7.0.2 + + '@prisma/config@7.10.0': + dependencies: + c12: 3.3.4 + deepmerge-ts: 8.0.0 + effect: 3.20.0 + empathic: 2.0.0 + transitivePeerDependencies: + - magicast + + '@prisma/debug@7.10.0': {} + + '@prisma/debug@7.2.0': {} + + '@prisma/dev@0.24.17(typescript@7.0.2)': + dependencies: + '@electric-sql/pglite': 0.4.3 + '@electric-sql/pglite-socket': 0.1.3(@electric-sql/pglite@0.4.3) + '@electric-sql/pglite-tools': 0.3.3(@electric-sql/pglite@0.4.3) + '@prisma/get-platform': 7.2.0 + '@prisma/query-plan-executor': 7.2.0 + '@prisma/streams-local': 0.1.11 + find-my-way: 9.7.0 + foreground-child: 3.3.1 + get-port-please: 3.2.0 + pathe: 2.0.3 + proper-lockfile: 4.1.2 + remeda: 2.33.4 + std-env: 3.10.0 + valibot: 1.4.2(typescript@7.0.2) + zeptomatch: 2.1.0 + transitivePeerDependencies: + - typescript + + '@prisma/driver-adapter-utils@7.10.0': + dependencies: + '@prisma/debug': 7.10.0 + + '@prisma/engines-version@7.10.0-4.0edf323efd1d98336f3f0a68684b56f689b900d3': {} + + '@prisma/engines@7.10.0': + dependencies: + '@prisma/debug': 7.10.0 + '@prisma/engines-version': 7.10.0-4.0edf323efd1d98336f3f0a68684b56f689b900d3 + '@prisma/fetch-engine': 7.10.0 + '@prisma/get-platform': 7.10.0 + + '@prisma/fetch-engine@7.10.0': + dependencies: + '@prisma/debug': 7.10.0 + '@prisma/engines-version': 7.10.0-4.0edf323efd1d98336f3f0a68684b56f689b900d3 + '@prisma/get-platform': 7.10.0 + + '@prisma/get-platform@7.10.0': + dependencies: + '@prisma/debug': 7.10.0 + + '@prisma/get-platform@7.2.0': + dependencies: + '@prisma/debug': 7.2.0 + + '@prisma/query-plan-executor@7.2.0': {} + + '@prisma/streams-local@0.1.11': + dependencies: + ajv: 8.20.0 + better-result: 2.10.0 + env-paths: 3.0.0 + proper-lockfile: 4.1.2 + + '@prisma/studio-core@0.33.0(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@types/react': 19.2.18 + '@visx/curve': 4.0.1-alpha.0 + '@visx/event': 4.0.1-alpha.0 + '@visx/grid': 4.0.1-alpha.0(react@19.2.8) + '@visx/group': 4.0.1-alpha.0(react@19.2.8) + '@visx/responsive': 4.0.1-alpha.0(react@19.2.8) + '@visx/scale': 4.0.1-alpha.0 + '@visx/shape': 4.0.1-alpha.0(react@19.2.8) + d3-array: 3.2.4 + d3-shape: 3.2.0 + elkjs: 0.11.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + transitivePeerDependencies: + - '@types/react-dom' + + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.5': {} + + '@protobufjs/eventemitter@1.1.1': {} + + '@protobufjs/fetch@1.1.1': + dependencies: + '@protobufjs/aspromise': 1.1.2 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.2': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.2': {} + + '@radix-ui/number@1.1.3': {} + + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/primitive@1.1.7': {} + + '@radix-ui/react-accessible-icon@1.1.15(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-accordion@1.2.20(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collapsible': 1.1.20(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-alert-dialog@1.1.23(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-dialog': 1.1.23(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-arrow@1.1.15(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-aspect-ratio@1.1.15(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-avatar@1.2.6(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-checkbox@1.3.11(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-collapsible@1.1.20(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-collection@1.1.15(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.18)(react@19.2.8)': + dependencies: + react: 19.2.8 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-compose-refs@1.1.5(@types/react@19.2.18)(react@19.2.8)': + dependencies: + react: 19.2.8 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-context-menu@2.3.7(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-menu': 2.1.24(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-context@1.2.2(@types/react@19.2.18)(react@19.2.8)': + dependencies: + react: 19.2.8 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-dialog@1.1.23(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) + aria-hidden: 1.2.6 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + react-remove-scroll: 2.7.2(@types/react@19.2.18)(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-direction@1.1.4(@types/react@19.2.18)(react@19.2.8)': + dependencies: + react: 19.2.8 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-dismissable-layer@1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-dropdown-menu@2.1.24(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-menu': 2.1.24(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-focus-guards@1.1.6(@types/react@19.2.18)(react@19.2.8)': + dependencies: + react: 19.2.8 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-focus-scope@1.1.16(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-form@0.1.16(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-label': 2.1.15(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-hover-card@1.1.23(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-id@1.1.4(@types/react@19.2.18)(react@19.2.8)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-label@2.1.15(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-menu@2.1.24(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8) + aria-hidden: 1.2.6 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + react-remove-scroll: 2.7.2(@types/react@19.2.18)(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-menubar@1.1.24(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-menu': 2.1.24(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-navigation-menu@1.2.22(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-previous': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-one-time-password-field@0.1.16(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/number': 1.1.3 + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-password-toggle-field@0.1.11(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-popover@1.1.23(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) + aria-hidden: 1.2.6 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + react-remove-scroll: 2.7.2(@types/react@19.2.18)(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-popper@1.3.7(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@floating-ui/react-dom': 2.1.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-arrow': 1.1.15(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-rect': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/rect': 1.1.3 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-portal@1.1.17(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-presence@1.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-primitive@2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-progress@1.1.16(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-radio-group@1.4.7(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-roving-focus@1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-scroll-area@1.2.18(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/number': 1.1.3 + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-select@2.3.7(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/number': 1.1.3 + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-previous': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + aria-hidden: 1.2.6 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + react-remove-scroll: 2.7.2(@types/react@19.2.18)(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-separator@1.1.15(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-slider@1.4.7(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/number': 1.1.3 + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-previous': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-slot@1.2.3(@types/react@19.2.18)(react@19.2.8)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-slot@1.3.3(@types/react@19.2.18)(react@19.2.8)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-switch@1.3.7(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-tabs@1.1.21(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-toast@1.2.23(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-toggle-group@1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-toggle': 1.1.18(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-toggle@1.1.18(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-toolbar@1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-separator': 1.1.15(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-toggle-group': 1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-tooltip@1.2.16(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/react-use-callback-ref@1.1.4(@types/react@19.2.18)(react@19.2.8)': + dependencies: + react: 19.2.8 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.18)(react@19.2.8)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-use-controllable-state@1.2.6(@types/react@19.2.18)(react@19.2.8)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.18)(react@19.2.8)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-use-effect-event@0.0.5(@types/react@19.2.18)(react@19.2.8)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-use-escape-keydown@1.1.5(@types/react@19.2.18)(react@19.2.8)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-use-is-hydrated@0.1.3(@types/react@19.2.18)(react@19.2.8)': + dependencies: + react: 19.2.8 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.18)(react@19.2.8)': + dependencies: + react: 19.2.8 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-use-layout-effect@1.1.4(@types/react@19.2.18)(react@19.2.8)': + dependencies: + react: 19.2.8 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-use-previous@1.1.4(@types/react@19.2.18)(react@19.2.8)': + dependencies: + react: 19.2.8 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-use-rect@1.1.4(@types/react@19.2.18)(react@19.2.8)': + dependencies: + '@radix-ui/rect': 1.1.3 + react: 19.2.8 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-use-size@1.1.4(@types/react@19.2.18)(react@19.2.8)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-visually-hidden@1.2.11(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + '@radix-ui/rect@1.1.3': {} + + '@redis/bloom@6.2.1(@redis/client@6.2.1)': + dependencies: + '@redis/client': 6.2.1 + + '@redis/client@6.2.1': + dependencies: + cluster-key-slot: 1.1.2 + + '@redis/json@6.2.1(@redis/client@6.2.1)': + dependencies: + '@redis/client': 6.2.1 + + '@redis/search@6.2.1(@redis/client@6.2.1)': + dependencies: + '@redis/client': 6.2.1 + + '@redis/time-series@6.2.1(@redis/client@6.2.1)': + dependencies: + '@redis/client': 6.2.1 + + '@rolldown/pluginutils@1.0.1': {} + + '@sideway/address@4.1.5': + dependencies: + '@hapi/hoek': 9.3.0 + + '@sideway/formula@3.0.1': {} + + '@sideway/pinpoint@2.0.0': {} + + '@standard-schema/spec@1.1.0': {} + + '@tailwindcss/node@4.3.3': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.24.5 + jiti: 2.7.0 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.3.3 + + '@tailwindcss/oxide-android-arm64@4.3.3': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.3.3': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.3.3': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.3.3': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.3.3': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.3.3': + optional: true + + '@tailwindcss/oxide@4.3.3': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.3.3 + '@tailwindcss/oxide-darwin-arm64': 4.3.3 + '@tailwindcss/oxide-darwin-x64': 4.3.3 + '@tailwindcss/oxide-freebsd-x64': 4.3.3 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.3 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.3 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.3 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.3 + '@tailwindcss/oxide-linux-x64-musl': 4.3.3 + '@tailwindcss/oxide-wasm32-wasi': 4.3.3 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.3 + + '@tailwindcss/vite@4.3.3(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0))': + dependencies: + '@tailwindcss/node': 4.3.3 + '@tailwindcss/oxide': 4.3.3 + tailwindcss: 4.3.3 + vite: '@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0)' + + '@tanstack/history@1.162.1': {} + + '@tanstack/query-core@5.102.8': {} + + '@tanstack/react-query@5.102.8(react@19.2.8)': + dependencies: + '@tanstack/query-core': 5.102.8 + react: 19.2.8 + + '@tanstack/react-router@1.170.32(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@tanstack/history': 1.162.1 + '@tanstack/react-store': 0.9.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@tanstack/router-core': 1.171.27 + isbot: 5.2.2 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@tanstack/react-store@0.9.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@tanstack/store': 0.9.3 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + use-sync-external-store: 1.6.0(react@19.2.8) + + '@tanstack/router-core@1.171.27': + dependencies: + '@tanstack/history': 1.162.1 + cookie-es: 3.1.1 + seroval: 1.6.4 + seroval-plugins: 1.6.4(seroval@1.6.4) + + '@tanstack/store@0.9.3': {} + + '@testcontainers/postgresql@12.1.0(supports-color@8.1.1)': + dependencies: + testcontainers: 12.1.0(supports-color@8.1.1) + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + - supports-color + + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/runtime': 7.29.7 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/user-event@14.6.7(@testing-library/dom@10.4.1)': + dependencies: + '@testing-library/dom': 10.4.1 + + '@types/aria-query@5.0.4': {} + + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 24.13.3 + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 24.13.3 + + '@types/d3-array@3.0.3': {} + + '@types/d3-color@3.1.0': {} + + '@types/d3-delaunay@6.0.1': {} + + '@types/d3-format@3.0.1': {} + + '@types/d3-geo@3.1.0': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-interpolate@3.0.1': + dependencies: + '@types/d3-color': 3.1.0 + + '@types/d3-path@3.1.1': {} + + '@types/d3-scale@4.0.2': + dependencies: + '@types/d3-time': 3.0.0 + + '@types/d3-shape@3.1.7': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time-format@2.1.0': {} + + '@types/d3-time@3.0.0': {} + + '@types/deep-eql@4.0.2': {} + + '@types/docker-modem@3.0.6': + dependencies: + '@types/node': 24.13.3 + '@types/ssh2': 1.15.6 + + '@types/dockerode@4.0.1': + dependencies: + '@types/docker-modem': 3.0.6 + '@types/node': 24.13.3 + '@types/ssh2': 1.15.6 + + '@types/estree@1.0.9': {} + + '@types/express-http-proxy@1.6.7': + dependencies: + '@types/express': 5.0.6 + + '@types/express-serve-static-core@5.1.3': + dependencies: + '@types/node': 24.13.3 + '@types/qs': 6.15.1 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 + + '@types/express@5.0.6': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.1.3 + '@types/serve-static': 2.2.0 + + '@types/geojson@7946.0.16': {} + + '@types/http-errors@2.0.5': {} + + '@types/lodash@4.17.25': {} + + '@types/long@4.0.2': {} + + '@types/node@18.19.130': + dependencies: + undici-types: 5.26.5 + + '@types/node@24.13.3': + dependencies: + undici-types: 7.18.2 + + '@types/pg@8.23.1': + dependencies: + '@types/node': 24.13.3 + pg-protocol: 1.16.0 + pg-types: 2.2.0 + + '@types/qs@6.15.1': {} + + '@types/range-parser@1.2.7': {} + + '@types/react-dom@19.2.7(@types/react@19.2.18)': + dependencies: + '@types/react': 19.2.18 + + '@types/react@19.2.18': + dependencies: + csstype: 3.2.3 + + '@types/send@1.2.1': + dependencies: + '@types/node': 24.13.3 + + '@types/serve-static@2.2.0': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 24.13.3 + + '@types/ssh2-streams@0.1.13': + dependencies: + '@types/node': 24.13.3 + + '@types/ssh2@0.5.52': + dependencies: + '@types/node': 24.13.3 + '@types/ssh2-streams': 0.1.13 + + '@types/ssh2@1.15.6': + dependencies: + '@types/node': 18.19.130 + + '@typescript/typescript-aix-ppc64@7.0.2': + optional: true + + '@typescript/typescript-darwin-arm64@7.0.2': + optional: true + + '@typescript/typescript-darwin-x64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-x64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm@7.0.2': + optional: true + + '@typescript/typescript-linux-loong64@7.0.2': + optional: true + + '@typescript/typescript-linux-mips64el@7.0.2': + optional: true + + '@typescript/typescript-linux-ppc64@7.0.2': + optional: true + + '@typescript/typescript-linux-riscv64@7.0.2': + optional: true + + '@typescript/typescript-linux-s390x@7.0.2': + optional: true + + '@typescript/typescript-linux-x64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-sunos-x64@7.0.2': + optional: true + + '@typescript/typescript-win32-arm64@7.0.2': + optional: true + + '@typescript/typescript-win32-x64@7.0.2': + optional: true + + '@visx/curve@4.0.1-alpha.0': + dependencies: + '@visx/vendor': 4.0.0-alpha.0 + + '@visx/event@4.0.1-alpha.0': + dependencies: + '@types/react': 19.2.18 + '@visx/point': 4.0.1-alpha.0 + + '@visx/grid@4.0.1-alpha.0(react@19.2.8)': + dependencies: + '@types/react': 19.2.18 + '@visx/curve': 4.0.1-alpha.0 + '@visx/group': 4.0.1-alpha.0(react@19.2.8) + '@visx/point': 4.0.1-alpha.0 + '@visx/scale': 4.0.1-alpha.0 + '@visx/shape': 4.0.1-alpha.0(react@19.2.8) + classnames: 2.5.1 + react: 19.2.8 + + '@visx/group@4.0.1-alpha.0(react@19.2.8)': + dependencies: + '@types/react': 19.2.18 + classnames: 2.5.1 + react: 19.2.8 + + '@visx/point@4.0.1-alpha.0': {} + + '@visx/responsive@4.0.1-alpha.0(react@19.2.8)': + dependencies: + '@types/lodash': 4.17.25 + '@types/react': 19.2.18 + lodash: 4.18.1 + react: 19.2.8 + + '@visx/scale@4.0.1-alpha.0': + dependencies: + '@visx/vendor': 4.0.0-alpha.0 + + '@visx/shape@4.0.1-alpha.0(react@19.2.8)': + dependencies: + '@types/lodash': 4.17.25 + '@types/react': 19.2.18 + '@visx/curve': 4.0.1-alpha.0 + '@visx/group': 4.0.1-alpha.0(react@19.2.8) + '@visx/scale': 4.0.1-alpha.0 + '@visx/vendor': 4.0.0-alpha.0 + classnames: 2.5.1 + lodash: 4.18.1 + react: 19.2.8 + + '@visx/vendor@4.0.0-alpha.0': + dependencies: + '@types/d3-array': 3.0.3 + '@types/d3-color': 3.1.0 + '@types/d3-delaunay': 6.0.1 + '@types/d3-format': 3.0.1 + '@types/d3-geo': 3.1.0 + '@types/d3-interpolate': 3.0.1 + '@types/d3-path': 3.1.1 + '@types/d3-scale': 4.0.2 + '@types/d3-shape': 3.1.7 + '@types/d3-time': 3.0.0 + '@types/d3-time-format': 2.1.0 + d3-array: 3.2.1 + d3-color: 3.1.0 + d3-delaunay: 6.0.2 + d3-format: 3.1.0 + d3-geo: 3.1.0 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + internmap: 2.0.3 + + '@vitejs/plugin-react@6.1.1(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0))': + dependencies: + '@rolldown/pluginutils': 1.0.1 + vite: '@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0)' + + '@vitest/browser-preview@4.1.11(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0))(vitest@4.1.11)': + dependencies: + '@testing-library/dom': 10.4.1 + '@testing-library/user-event': 14.6.7(@testing-library/dom@10.4.1) + '@vitest/browser': 4.1.11(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0))(vitest@4.1.11) + vitest: 4.1.11(@types/node@24.13.3)(@vitest/browser-preview@4.1.11)(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0)) + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + + '@vitest/browser@4.1.11(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0))(vitest@4.1.11)': + dependencies: + '@blazediff/core': 1.9.1 + '@vitest/mocker': 4.1.11(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0)) + '@vitest/utils': 4.1.11 + magic-string: 0.30.21 + pngjs: 7.0.0 + sirv: 3.0.2 + tinyrainbow: 3.1.1 + vitest: 4.1.11(@types/node@24.13.3)(@vitest/browser-preview@4.1.11)(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0)) + ws: 8.21.3 + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + + '@vitest/expect@4.1.11': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.11 + '@vitest/utils': 4.1.11 + chai: 6.2.2 + tinyrainbow: 3.1.1 + + '@vitest/mocker@4.1.11(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0))': + dependencies: + '@vitest/spy': 4.1.11 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: '@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0)' + + '@vitest/pretty-format@4.1.11': + dependencies: + tinyrainbow: 3.1.1 + + '@vitest/runner@4.1.11': + dependencies: + '@vitest/utils': 4.1.11 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.11': + dependencies: + '@vitest/pretty-format': 4.1.11 + '@vitest/utils': 4.1.11 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.11': {} + + '@vitest/utils@4.1.11': + dependencies: + '@vitest/pretty-format': 4.1.11 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.1 + + '@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0)': + dependencies: + '@oxc-project/runtime': 0.146.0 + '@oxc-project/types': 0.146.0 + lightningcss: 1.33.0 + postcss: 8.5.28 + yuku-codegen: 0.5.48 + yuku-parser: 0.5.48 + optionalDependencies: + '@types/node': 24.13.3 + '@voidzero-dev/vite-plus-darwin-arm64': 0.3.0 + '@voidzero-dev/vite-plus-darwin-x64': 0.3.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 0.3.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 0.3.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 0.3.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 0.3.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 0.3.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 0.3.0 + esbuild: 0.28.2 + fsevents: 2.3.3 + jiti: 2.7.0 + terser: 5.51.2 + tsx: 4.23.13 + typescript: 7.0.2 + yaml: 2.9.0 + + '@voidzero-dev/vite-plus-darwin-arm64@0.3.0': + optional: true + + '@voidzero-dev/vite-plus-darwin-x64@0.3.0': + optional: true + + '@voidzero-dev/vite-plus-linux-arm64-gnu@0.3.0': + optional: true + + '@voidzero-dev/vite-plus-linux-arm64-musl@0.3.0': + optional: true + + '@voidzero-dev/vite-plus-linux-x64-gnu@0.3.0': + optional: true + + '@voidzero-dev/vite-plus-linux-x64-musl@0.3.0': + optional: true + + '@voidzero-dev/vite-plus-win32-arm64-msvc@0.3.0': + optional: true + + '@voidzero-dev/vite-plus-win32-x64-msvc@0.3.0': + optional: true + + '@whatwg-node/promise-helpers@1.3.2': + dependencies: + tslib: 2.8.1 + + '@yuku-codegen/binding-darwin-arm64@0.5.48': + optional: true + + '@yuku-codegen/binding-darwin-x64@0.5.48': + optional: true + + '@yuku-codegen/binding-freebsd-x64@0.5.48': + optional: true + + '@yuku-codegen/binding-linux-arm-gnu@0.5.48': + optional: true + + '@yuku-codegen/binding-linux-arm-musl@0.5.48': + optional: true + + '@yuku-codegen/binding-linux-arm64-gnu@0.5.48': + optional: true + + '@yuku-codegen/binding-linux-arm64-musl@0.5.48': + optional: true + + '@yuku-codegen/binding-linux-x64-gnu@0.5.48': + optional: true + + '@yuku-codegen/binding-linux-x64-musl@0.5.48': + optional: true + + '@yuku-codegen/binding-win32-arm64@0.5.48': + optional: true + + '@yuku-codegen/binding-win32-x64@0.5.48': + optional: true + + '@yuku-parser/binding-darwin-arm64@0.5.48': + optional: true + + '@yuku-parser/binding-darwin-x64@0.5.48': + optional: true + + '@yuku-parser/binding-freebsd-x64@0.5.48': + optional: true + + '@yuku-parser/binding-linux-arm-gnu@0.5.48': + optional: true + + '@yuku-parser/binding-linux-arm-musl@0.5.48': + optional: true + + '@yuku-parser/binding-linux-arm64-gnu@0.5.48': + optional: true + + '@yuku-parser/binding-linux-arm64-musl@0.5.48': + optional: true + + '@yuku-parser/binding-linux-x64-gnu@0.5.48': + optional: true + + '@yuku-parser/binding-linux-x64-musl@0.5.48': + optional: true + + '@yuku-parser/binding-win32-arm64@0.5.48': + optional: true + + '@yuku-parser/binding-win32-x64@0.5.48': + optional: true + + '@yuku-toolchain/types@0.5.43': {} + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.1.0 + + acorn@8.18.0: + optional: true + + agent-base@6.0.2(supports-color@8.1.1): + dependencies: + debug: 4.4.3(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.7 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + archiver-utils@5.0.2: + dependencies: + glob: 13.0.6 + graceful-fs: 4.2.11 + is-stream: 2.0.1 + lazystream: 1.0.1 + lodash: 4.18.1 + normalize-path: 3.0.0 + readable-stream: 4.7.0 + + archiver@7.0.1: + dependencies: + archiver-utils: 5.0.2 + async: 3.2.6 + buffer-crc32: 1.0.0 + readable-stream: 4.7.0 + readdir-glob: 1.1.3 + tar-stream: 3.2.1 + zip-stream: 6.0.1 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-union@2.1.0: {} + + array.prototype.reduce@1.0.8: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-array-method-boxes-properly: 1.0.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + is-string: 1.1.1 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + asn1@0.2.6: + dependencies: + safer-buffer: 2.1.2 + + assertion-error@2.0.1: {} + + async-function@1.0.0: {} + + async-lock@1.4.1: {} + + async-retry@1.3.3: + dependencies: + retry: 0.13.1 + + async@3.2.6: {} + + asynckit@0.4.0: {} + + auto-bind@5.0.1: {} + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + aws-ssl-profiles@1.1.2: {} + + axios@1.20.0(debug@4.4.3(supports-color@8.1.1))(supports-color@8.1.1): + dependencies: + follow-redirects: 1.16.0(debug@4.4.3(supports-color@8.1.1)) + form-data: 4.0.6 + https-proxy-agent: 5.0.1(supports-color@8.1.1) + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + - supports-color + + b4a@1.8.1: {} + + balanced-match@1.0.2: {} + + balanced-match@4.0.4: {} + + bare-events@2.9.2: {} + + bare-fs@4.8.1: + dependencies: + bare-events: 2.9.2 + bare-path: 3.1.2 + bare-stream: 2.13.4(bare-events@2.9.2) + bare-url: 2.5.4 + fast-fifo: 1.3.2 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + bare-path@3.1.2: {} + + bare-stream@2.13.4(bare-events@2.9.2): + dependencies: + b4a: 1.8.1 + streamx: 2.28.1 + teex: 1.0.1 + optionalDependencies: + bare-events: 2.9.2 + transitivePeerDependencies: + - react-native-b4a + + bare-url@2.5.4: + dependencies: + bare-path: 3.1.2 + + base64-js@1.5.1: {} + + base64url@3.0.1: {} + + bcrypt-pbkdf@1.0.2: + dependencies: + tweetnacl: 0.14.5 + + better-result@2.10.0: {} + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + body-parser@2.3.0(supports-color@8.1.1): + dependencies: + bytes: 3.1.2 + content-type: 2.1.0 + debug: 4.4.3(supports-color@8.1.1) + http-errors: 2.0.1 + iconv-lite: 0.7.3 + on-finished: 2.4.1 + qs: 6.16.0 + raw-body: 3.0.2 + type-is: 2.1.0 + transitivePeerDependencies: + - supports-color + + brace-expansion@2.1.4: + dependencies: + balanced-match: 1.0.2 + + brace-expansion@5.0.9: + dependencies: + balanced-match: 4.0.4 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + buffer-crc32@1.0.0: {} + + buffer-from@1.1.2: + optional: true + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + buildcheck@0.0.7: + optional: true + + byline@5.0.0: {} + + bytes@3.1.2: {} + + c12@3.3.4: + dependencies: + chokidar: 5.0.0 + confbox: 0.2.4 + defu: 6.1.7 + dotenv: 17.4.2 + exsolve: 1.1.1 + giget: 3.3.1 + jiti: 2.7.0 + ohash: 2.0.12 + pathe: 2.0.3 + perfect-debounce: 2.1.0 + pkg-types: 2.3.2 + rc9: 3.1.0 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.9: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + chai@6.2.2: {} + + change-case-all@2.1.0: + dependencies: + change-case: 5.4.4 + sponge-case: 2.0.3 + swap-case: 3.0.3 + title-case: 3.0.3 + + change-case@5.4.4: {} + + chokidar@5.0.0: + dependencies: + readdirp: 5.1.1 + + chownr@1.1.4: {} + + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + classnames@2.5.1: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone@2.1.2: {} + + clsx@2.1.1: {} + + cluster-key-slot@1.1.2: {} + + cn@0.2.5: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@2.20.3: + optional: true + + common-tags@1.8.2: {} + + compress-commons@6.0.2: + dependencies: + crc-32: 1.2.2 + crc32-stream: 6.0.0 + is-stream: 2.0.1 + normalize-path: 3.0.0 + readable-stream: 4.7.0 + + confbox@0.2.4: {} + + confbox@0.3.1: {} + + content-disposition@1.1.0: {} + + content-type@1.0.5: {} + + content-type@2.1.0: {} + + convert-source-map@2.0.0: {} + + cookie-es@3.1.1: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + core-util-is@1.0.3: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cpu-features@0.0.10: + dependencies: + buildcheck: 0.0.7 + nan: 2.28.0 + optional: true + + crc-32@1.2.2: {} + + crc32-stream@6.0.0: + dependencies: + crc-32: 1.2.2 + readable-stream: 4.7.0 + + cross-inspect@1.0.1: + dependencies: + tslib: 2.8.1 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + csstype@3.2.3: {} + + d3-array@3.2.1: + dependencies: + internmap: 2.0.3 + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-color@3.1.0: {} + + d3-delaunay@6.0.2: + dependencies: + delaunator: 5.1.0 + + d3-format@3.1.0: {} + + d3-geo@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@3.1.0: {} + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.0 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + date-fns@4.4.0: {} + + debug@3.2.7(supports-color@8.1.1): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 + + debug@4.4.3(supports-color@8.1.1): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 + + deepmerge-ts@8.0.0: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + defu@6.1.7: {} + + delaunator@5.1.0: + dependencies: + robust-predicates: 3.0.3 + + delayed-stream@1.0.0: {} + + depd@1.1.2: {} + + depd@2.0.0: {} + + dependency-graph@1.0.0: {} + + dequal@2.0.3: {} + + destr@2.0.5: {} + + detect-libc@2.1.2: {} + + detect-node-es@1.1.0: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + docker-compose@1.4.2: + dependencies: + yaml: 2.9.0 + + docker-modem@5.0.7(supports-color@8.1.1): + dependencies: + debug: 4.4.3(supports-color@8.1.1) + readable-stream: 3.6.2 + split-ca: 1.0.1 + ssh2: 1.17.0 + transitivePeerDependencies: + - supports-color + + dockerode@5.0.1(supports-color@8.1.1): + dependencies: + '@balena/dockerignore': 1.0.2 + '@grpc/grpc-js': 1.14.4 + '@grpc/proto-loader': 0.7.15 + docker-modem: 5.0.7(supports-color@8.1.1) + protobufjs: 7.6.6 + tar-fs: 2.1.5 + transitivePeerDependencies: + - supports-color + + dom-accessibility-api@0.5.16: {} + + dotenv@17.4.2: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + effect@3.20.0: + dependencies: + '@standard-schema/spec': 1.1.0 + fast-check: 3.23.2 + + elkjs@0.11.1: {} + + emoji-regex@8.0.0: {} + + empathic@2.0.0: {} + + encodeurl@2.0.0: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + enhanced-resolve@5.24.5: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.3 + + env-paths@3.0.0: {} + + es-abstract-get@1.0.0: + dependencies: + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + is-callable: 1.2.7 + object-inspect: 1.13.4 + + es-abstract@1.24.2: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.4 + function.prototype.name: 1.2.0 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.2 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.4 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.11 + string.prototype.trimend: 1.0.10 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.8 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.22 + + es-array-method-boxes-properly@1.0.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@2.3.2: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + es-to-primitive@1.3.4: + dependencies: + es-abstract-get: 1.0.0 + es-define-property: 1.0.1 + es-errors: 1.3.0 + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + es6-promise@4.2.8: {} + + esbuild@0.28.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.2 + '@esbuild/android-arm': 0.28.2 + '@esbuild/android-arm64': 0.28.2 + '@esbuild/android-x64': 0.28.2 + '@esbuild/darwin-arm64': 0.28.2 + '@esbuild/darwin-x64': 0.28.2 + '@esbuild/freebsd-arm64': 0.28.2 + '@esbuild/freebsd-x64': 0.28.2 + '@esbuild/linux-arm': 0.28.2 + '@esbuild/linux-arm64': 0.28.2 + '@esbuild/linux-ia32': 0.28.2 + '@esbuild/linux-loong64': 0.28.2 + '@esbuild/linux-mips64el': 0.28.2 + '@esbuild/linux-ppc64': 0.28.2 + '@esbuild/linux-riscv64': 0.28.2 + '@esbuild/linux-s390x': 0.28.2 + '@esbuild/linux-x64': 0.28.2 + '@esbuild/netbsd-arm64': 0.28.2 + '@esbuild/netbsd-x64': 0.28.2 + '@esbuild/openbsd-arm64': 0.28.2 + '@esbuild/openbsd-x64': 0.28.2 + '@esbuild/openharmony-arm64': 0.28.2 + '@esbuild/sunos-x64': 0.28.2 + '@esbuild/win32-arm64': 0.28.2 + '@esbuild/win32-ia32': 0.28.2 + '@esbuild/win32-x64': 0.28.2 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + etag@1.8.1: {} + + event-target-shim@5.0.1: {} + + events-universal@1.0.1: + dependencies: + bare-events: 2.9.2 + transitivePeerDependencies: + - bare-abort-controller + + events@3.3.0: {} + + expect-type@1.4.0: {} + + express-http-proxy@2.1.2(supports-color@8.1.1): + dependencies: + debug: 3.2.7(supports-color@8.1.1) + es6-promise: 4.2.8 + raw-body: 2.5.3 + transitivePeerDependencies: + - supports-color + + express-openid-connect@3.4.0(express@5.2.1(supports-color@8.1.1))(supports-color@8.1.1): + dependencies: + base64url: 3.0.1 + clone: 2.1.2 + cookie: 0.7.2 + debug: 4.4.3(supports-color@8.1.1) + express: 5.2.1(supports-color@8.1.1) + http-errors: 1.8.1 + joi: 17.13.7 + jose: 6.2.11 + on-headers: 1.1.0 + openid-client: 6.8.7 + url-join: 4.0.1 + util-promisify: 3.0.0 + transitivePeerDependencies: + - supports-color + + express@5.2.1(supports-color@8.1.1): + dependencies: + accepts: 2.0.0 + body-parser: 2.3.0(supports-color@8.1.1) + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3(supports-color@8.1.1) + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1(supports-color@8.1.1) + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.16.0 + range-parser: 1.3.0 + router: 2.2.0(supports-color@8.1.1) + send: 1.2.1(supports-color@8.1.1) + serve-static: 2.2.1(supports-color@8.1.1) + statuses: 2.0.2 + type-is: 2.1.0 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + exsolve@1.1.1: {} + + fast-check@3.23.2: + dependencies: + pure-rand: 6.1.0 + + fast-decode-uri-component@1.0.1: {} + + fast-deep-equal@3.1.3: {} + + fast-fifo@1.3.2: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-querystring@1.1.2: + dependencies: + fast-decode-uri-component: 1.0.1 + + fast-uri@3.1.7: {} + + fastq@1.20.3: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@2.1.1(supports-color@8.1.1): + dependencies: + debug: 4.4.3(supports-color@8.1.1) + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + find-my-way@9.7.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-querystring: 1.1.2 + safe-regex2: 5.1.1 + + follow-redirects@1.16.0(debug@4.4.3(supports-color@8.1.1)): + optionalDependencies: + debug: 4.4.3(supports-color@8.1.1) + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + form-data@4.0.6: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 + mime-types: 2.1.35 + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + fs-constants@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.2.0: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + es-define-property: 1.0.1 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + hasown: 2.0.4 + is-callable: 1.2.7 + is-document.all: 1.0.0 + + functions-have-names@1.2.3: {} + + generate-function@2.3.1: + dependencies: + is-property: 1.0.2 + + generator-function@2.0.1: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-nonce@1.0.1: {} + + get-port-please@3.2.0: {} + + get-port@5.1.1: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + giget@3.3.1: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob@13.0.6: + dependencies: + minimatch: 10.2.6 + minipass: 7.1.3 + path-scurry: 2.0.2 + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + grammex@3.1.13: {} + + graphmatch@1.1.1: {} + + graphql-tag@2.12.7(graphql@16.14.2): + dependencies: + graphql: 16.14.2 + tslib: 2.8.1 + + graphql@16.14.2: {} + + has-bigints@1.1.0: {} + + has-flag@4.0.0: + optional: true + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + http-errors@1.8.1: + dependencies: + depd: 1.1.2 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 1.5.0 + toidentifier: 1.0.1 + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + https-proxy-agent@5.0.1(supports-color@8.1.1): + dependencies: + agent-base: 6.0.2(supports-color@8.1.1) + debug: 4.4.3(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + iconv-lite@0.7.3: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + immutable@5.1.9: {} + + import-from@4.0.0: {} + + inherits@2.0.4: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.4 + side-channel: 1.1.1 + + internmap@2.0.3: {} + + invariant@2.2.4: + dependencies: + loose-envify: 1.4.0 + + ipaddr.js@1.9.1: {} + + is-absolute@1.0.0: + dependencies: + is-relative: 1.0.0 + is-windows: 1.0.2 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-document.all@1.0.0: + dependencies: + call-bound: 1.0.4 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-fullwidth-code-point@3.0.0: {} + + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-promise@4.0.0: {} + + is-property@1.0.2: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + is-relative@1.0.0: + dependencies: + is-unc-path: 1.0.0 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-stream@2.0.1: {} + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.22 + + is-unc-path@1.0.0: + dependencies: + unc-path-regex: 0.1.2 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-windows@1.0.2: {} + + isarray@1.0.0: {} + + isarray@2.0.5: {} + + isbot@5.2.2: {} + + isexe@2.0.0: {} + + jiti@2.7.0: {} + + joi@17.13.7: + dependencies: + '@hapi/hoek': 9.3.0 + '@hapi/topo': 5.1.0 + '@sideway/address': 4.1.5 + '@sideway/formula': 3.0.1 + '@sideway/pinpoint': 2.0.0 + + jose@6.2.11: {} + + js-tokens@4.0.0: {} + + json-schema-traverse@1.0.0: {} + + lazystream@1.0.1: + dependencies: + readable-stream: 2.3.8 + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-android-arm64@1.33.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.33.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.33.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.33.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.33.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.33.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.33.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.33.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + + lodash.camelcase@4.3.0: {} + + lodash.sortby@4.7.0: {} + + lodash@4.18.1: {} + + loglevel@1.9.2: {} + + long@4.0.0: {} + + long@5.3.2: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@11.5.2: {} + + lru.min@1.1.5: {} + + lucide-react@1.41.0(react@19.2.8): + dependencies: + react: 19.2.8 + + lz-string@1.5.0: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + + map-cache@0.2.2: {} + + math-intrinsics@1.1.0: {} + + media-typer@1.1.1: {} + + merge-descriptors@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + mime-db@1.52.0: {} + + mime-db@1.54.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + minimatch@10.2.6: + dependencies: + brace-expansion: 5.0.9 + + minimatch@5.1.9: + dependencies: + brace-expansion: 2.1.4 + + minipass@7.1.3: {} + + mkdirp-classic@0.5.3: {} + + mkdirp@3.0.1: {} + + mrmime@2.0.1: {} + + ms@2.1.3: {} + + mysql2@3.24.3(@types/node@24.13.3): + dependencies: + '@types/node': 24.13.3 + aws-ssl-profiles: 1.1.2 + generate-function: 2.3.1 + iconv-lite: 0.7.3 + long: 5.3.2 + lru.min: 1.1.5 + named-placeholders: 1.1.6 + sql-escaper: 1.5.1 + + named-placeholders@1.1.6: + dependencies: + lru.min: 1.1.5 + + nan@2.28.0: + optional: true + + nanoid@3.3.18: {} + + negotiator@1.1.0: + dependencies: + content-type: 2.1.0 + + normalize-path@2.1.1: + dependencies: + remove-trailing-separator: 1.1.0 + + normalize-path@3.0.0: {} + + oauth4webapi@3.8.7: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.getownpropertydescriptors@2.1.9: + dependencies: + array.prototype.reduce: 1.0.8 + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + gopd: 1.2.0 + safe-array-concat: 1.1.4 + + obug@2.1.4: {} + + ohash@2.0.12: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + on-headers@1.1.0: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + openid-client@6.8.7: + dependencies: + jose: 6.2.11 + oauth4webapi: 3.8.7 + + own-keys@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + oxfmt@0.64.0(vite-plus@0.3.0(@types/node@24.13.3)(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0)): + dependencies: + tinypool: 2.1.0 + optionalDependencies: + '@oxfmt/binding-android-arm-eabi': 0.64.0 + '@oxfmt/binding-android-arm64': 0.64.0 + '@oxfmt/binding-darwin-arm64': 0.64.0 + '@oxfmt/binding-darwin-x64': 0.64.0 + '@oxfmt/binding-freebsd-x64': 0.64.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.64.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.64.0 + '@oxfmt/binding-linux-arm64-gnu': 0.64.0 + '@oxfmt/binding-linux-arm64-musl': 0.64.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.64.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.64.0 + '@oxfmt/binding-linux-riscv64-musl': 0.64.0 + '@oxfmt/binding-linux-s390x-gnu': 0.64.0 + '@oxfmt/binding-linux-x64-gnu': 0.64.0 + '@oxfmt/binding-linux-x64-musl': 0.64.0 + '@oxfmt/binding-openharmony-arm64': 0.64.0 + '@oxfmt/binding-win32-arm64-msvc': 0.64.0 + '@oxfmt/binding-win32-ia32-msvc': 0.64.0 + '@oxfmt/binding-win32-x64-msvc': 0.64.0 + vite-plus: 0.3.0(@types/node@24.13.3)(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0) + + oxlint-tsgolint@7.0.2001: + optionalDependencies: + '@oxlint-tsgolint/darwin-arm64': 7.0.2001 + '@oxlint-tsgolint/darwin-x64': 7.0.2001 + '@oxlint-tsgolint/linux-arm64': 7.0.2001 + '@oxlint-tsgolint/linux-x64': 7.0.2001 + '@oxlint-tsgolint/win32-arm64': 7.0.2001 + '@oxlint-tsgolint/win32-x64': 7.0.2001 + + oxlint@1.79.0(oxlint-tsgolint@7.0.2001)(vite-plus@0.3.0(@types/node@24.13.3)(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0)): + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.79.0 + '@oxlint/binding-android-arm64': 1.79.0 + '@oxlint/binding-darwin-arm64': 1.79.0 + '@oxlint/binding-darwin-x64': 1.79.0 + '@oxlint/binding-freebsd-x64': 1.79.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.79.0 + '@oxlint/binding-linux-arm-musleabihf': 1.79.0 + '@oxlint/binding-linux-arm64-gnu': 1.79.0 + '@oxlint/binding-linux-arm64-musl': 1.79.0 + '@oxlint/binding-linux-ppc64-gnu': 1.79.0 + '@oxlint/binding-linux-riscv64-gnu': 1.79.0 + '@oxlint/binding-linux-riscv64-musl': 1.79.0 + '@oxlint/binding-linux-s390x-gnu': 1.79.0 + '@oxlint/binding-linux-x64-gnu': 1.79.0 + '@oxlint/binding-linux-x64-musl': 1.79.0 + '@oxlint/binding-openharmony-arm64': 1.79.0 + '@oxlint/binding-win32-arm64-msvc': 1.79.0 + '@oxlint/binding-win32-ia32-msvc': 1.79.0 + '@oxlint/binding-win32-x64-msvc': 1.79.0 + oxlint-tsgolint: 7.0.2001 + vite-plus: 0.3.0(@types/node@24.13.3)(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0) + + parse-filepath@1.0.2: + dependencies: + is-absolute: 1.0.0 + map-cache: 0.2.2 + path-root: 0.1.1 + + parseurl@1.3.3: {} + + path-key@3.1.1: {} + + path-root-regex@0.1.2: {} + + path-root@0.1.1: + dependencies: + path-root-regex: 0.1.2 + + path-scurry@2.0.2: + dependencies: + lru-cache: 11.5.2 + minipass: 7.1.3 + + path-to-regexp@8.4.2: {} + + path-type@4.0.0: {} + + pathe@2.0.3: {} + + perfect-debounce@2.1.0: {} + + pg-cloudflare@1.4.0: + optional: true + + pg-connection-string@2.14.0: {} + + pg-int8@1.0.1: {} + + pg-pool@3.14.0(pg@8.23.0): + dependencies: + pg: 8.23.0 + + pg-protocol@1.16.0: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.1 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + pg@8.23.0: + dependencies: + pg-connection-string: 2.14.0 + pg-pool: 3.14.0(pg@8.23.0) + pg-protocol: 1.16.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.4.0 + + pgpass@1.0.5: + dependencies: + split2: 4.2.0 + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.7: {} + + pkg-types@2.3.2: + dependencies: + confbox: 0.3.1 + exsolve: 1.1.1 + pathe: 2.0.3 + + playwright-core@1.63.0: {} + + playwright@1.63.0: + dependencies: + playwright-core: 1.63.0 + + pngjs@7.0.0: {} + + possible-typed-array-names@1.1.0: {} + + postcss@8.5.28: + dependencies: + nanoid: 3.3.18 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postgres-array@2.0.0: {} + + postgres-array@3.0.4: {} + + postgres-bytea@1.0.1: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + + postgres@3.4.7: {} + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.1 + + prisma@7.10.0(@types/node@24.13.3)(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2): + dependencies: + '@prisma/config': 7.10.0 + '@prisma/dev': 0.24.17(typescript@7.0.2) + '@prisma/engines': 7.10.0 + '@prisma/studio-core': 0.33.0(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + mysql2: 3.24.3(@types/node@24.13.3) + postgres: 3.4.7 + optionalDependencies: + typescript: 7.0.2 + transitivePeerDependencies: + - '@types/node' + - '@types/react' + - '@types/react-dom' + - magicast + - react + - react-dom + + process-nextick-args@2.0.1: {} + + process@0.11.10: {} + + proper-lockfile@4.1.2: + dependencies: + graceful-fs: 4.2.11 + retry: 0.12.0 + signal-exit: 3.0.7 + + properties-reader@3.0.1(supports-color@8.1.1): + dependencies: + '@kwsites/file-exists': 1.1.1(supports-color@8.1.1) + mkdirp: 3.0.1 + transitivePeerDependencies: + - supports-color + + protobufjs@7.6.6: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.5 + '@protobufjs/eventemitter': 1.1.1 + '@protobufjs/fetch': 1.1.1 + '@protobufjs/float': 1.0.2 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.2 + '@types/node': 24.13.3 + long: 5.3.2 + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + proxy-from-env@2.1.0: {} + + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + pure-rand@6.1.0: {} + + qs@6.16.0: + dependencies: + es-define-property: 1.0.1 + side-channel: 1.1.1 + + queue-microtask@1.2.3: {} + + radix-ui@1.6.7(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8): + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-accessible-icon': 1.1.15(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-accordion': 1.2.20(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-alert-dialog': 1.1.23(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-arrow': 1.1.15(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-aspect-ratio': 1.1.15(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-avatar': 1.2.6(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-checkbox': 1.3.11(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-collapsible': 1.1.20(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context-menu': 2.3.7(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-dialog': 1.1.23(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-dropdown-menu': 2.1.24(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-form': 0.1.16(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-hover-card': 1.1.23(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-label': 2.1.15(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-menu': 2.1.24(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-menubar': 1.1.24(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-navigation-menu': 1.2.22(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-one-time-password-field': 0.1.16(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-password-toggle-field': 0.1.11(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-popover': 1.1.23(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-progress': 1.1.16(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-radio-group': 1.4.7(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-scroll-area': 1.2.18(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-select': 2.3.7(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-separator': 1.1.15(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-slider': 1.4.7(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-switch': 1.3.7(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-tabs': 1.1.21(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-toast': 1.2.23(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-toggle': 1.1.18(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-toggle-group': 1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-toolbar': 1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-tooltip': 1.2.16(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-escape-keydown': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + + range-parser@1.3.0: {} + + raw-body@2.5.3: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + unpipe: 1.0.0 + + rc9@3.1.0: + dependencies: + defu: 6.1.7 + destr: 2.0.5 + + react-dom@19.2.8(react@19.2.8): + dependencies: + react: 19.2.8 + scheduler: 0.27.0 + + react-is@17.0.1: {} + + react-remove-scroll-bar@2.3.8(@types/react@19.2.18)(react@19.2.8): + dependencies: + react: 19.2.8 + react-style-singleton: 2.2.3(@types/react@19.2.18)(react@19.2.8) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.18 + + react-remove-scroll@2.7.2(@types/react@19.2.18)(react@19.2.8): + dependencies: + react: 19.2.8 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.18)(react@19.2.8) + react-style-singleton: 2.2.3(@types/react@19.2.18)(react@19.2.8) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.2.18)(react@19.2.8) + use-sidecar: 1.1.3(@types/react@19.2.18)(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + + react-style-singleton@2.2.3(@types/react@19.2.18)(react@19.2.8): + dependencies: + get-nonce: 1.0.1 + react: 19.2.8 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.18 + + react@19.2.8: {} + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readable-stream@4.7.0: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + + readdir-glob@1.1.3: + dependencies: + minimatch: 5.1.9 + + readdirp@5.1.1: {} + + redis@6.2.1: + dependencies: + '@redis/bloom': 6.2.1(@redis/client@6.2.1) + '@redis/client': 6.2.1 + '@redis/json': 6.2.1(@redis/client@6.2.1) + '@redis/search': 6.2.1(@redis/client@6.2.1) + '@redis/time-series': 6.2.1(@redis/client@6.2.1) + transitivePeerDependencies: + - '@node-rs/xxhash' + - '@opentelemetry/api' + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + remeda@2.33.4: {} + + remove-trailing-separator@1.1.0: {} + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + ret@0.5.0: {} + + retry@0.12.0: {} + + retry@0.13.1: {} + + reusify@1.1.0: {} + + robust-predicates@3.0.3: {} + + router@2.2.0(supports-color@8.1.1): + dependencies: + debug: 4.4.3(supports-color@8.1.1) + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-array-concat@1.1.4: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safe-regex2@5.1.1: + dependencies: + ret: 0.5.0 + + safer-buffer@2.1.2: {} + + scheduler@0.27.0: {} + + send@1.2.1(supports-color@8.1.1): + dependencies: + debug: 4.4.3(supports-color@8.1.1) + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.3.0 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + seroval-plugins@1.6.4(seroval@1.6.4): + dependencies: + seroval: 1.6.4 + + seroval@1.6.4: {} + + serve-static@2.2.1(supports-color@8.1.1): + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + + setprototypeof@1.2.0: {} + + sha.js@2.4.12: + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.1 + to-buffer: 1.2.2 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + siginfo@2.0.0: {} + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + + slash@3.0.0: {} + + sonner@2.0.8(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8): + dependencies: + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + + source-map-js@1.2.1: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + optional: true + + source-map@0.6.1: + optional: true + + split-ca@1.0.1: {} + + split2@4.2.0: {} + + sponge-case@2.0.3: {} + + sql-escaper@1.5.1: {} + + ssh-remote-port-forward@1.0.4: + dependencies: + '@types/ssh2': 0.5.52 + ssh2: 1.17.0 + + ssh2@1.17.0: + dependencies: + asn1: 0.2.6 + bcrypt-pbkdf: 1.0.2 + optionalDependencies: + cpu-features: 0.0.10 + nan: 2.28.0 + + stackback@0.0.2: {} + + statuses@1.5.0: {} + + statuses@2.0.2: {} + + std-env@3.10.0: {} + + std-env@4.2.0: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + streamx@2.28.1: + dependencies: + events-universal: 1.0.1 + fast-fifo: 1.3.2 + text-decoder: 1.2.7 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string.prototype.trim@1.2.11: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + has-property-descriptors: 1.0.2 + safe-regex-test: 1.1.0 + + string.prototype.trimend@1.0.10: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + optional: true + + swap-case@3.0.3: {} + + tailwind-merge@3.6.0: {} + + tailwindcss@4.3.3: {} + + tapable@2.3.3: {} + + tar-fs@2.1.5: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.4 + tar-stream: 2.2.0 + + tar-fs@3.1.3: + dependencies: + pump: 3.0.4 + tar-stream: 3.2.1 + optionalDependencies: + bare-fs: 4.8.1 + bare-path: 3.1.2 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + tar-stream@3.2.1: + dependencies: + b4a: 1.8.1 + bare-fs: 4.8.1 + fast-fifo: 1.3.2 + streamx: 2.28.1 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + teex@1.0.1: + dependencies: + streamx: 2.28.1 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + temporal-polyfill@1.0.4: + dependencies: + temporal-spec: 1.0.1 + temporal-utils: 1.0.2 + + temporal-spec@1.0.1: {} + + temporal-utils@1.0.2: {} + + terser@5.51.2: + dependencies: + '@jridgewell/source-map': 0.3.11 + acorn: 8.18.0 + commander: 2.20.3 + source-map-support: 0.5.21 + optional: true + + testcontainers@12.1.0(supports-color@8.1.1): + dependencies: + '@balena/dockerignore': 1.0.2 + '@types/dockerode': 4.0.1 + archiver: 7.0.1 + async-lock: 1.4.1 + byline: 5.0.0 + debug: 4.4.3(supports-color@8.1.1) + docker-compose: 1.4.2 + dockerode: 5.0.1(supports-color@8.1.1) + get-port: 5.1.1 + proper-lockfile: 4.1.2 + properties-reader: 3.0.1(supports-color@8.1.1) + ssh-remote-port-forward: 1.0.4 + tar-fs: 3.1.3 + tmp: 0.2.7 + undici: 8.10.2 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + - supports-color + + text-decoder@1.2.7: + dependencies: + b4a: 1.8.1 + transitivePeerDependencies: + - react-native-b4a + + tinybench@2.9.0: {} + + tinyexec@1.3.1: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + + tinypool@2.1.0: {} + + tinyrainbow@3.1.1: {} + + title-case@3.0.3: + dependencies: + tslib: 2.8.1 + + tmp@0.2.7: {} + + to-buffer@1.2.2: + dependencies: + isarray: 2.0.5 + safe-buffer: 5.2.1 + typed-array-buffer: 1.0.3 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + totalist@3.0.1: {} + + tslib@2.8.1: {} + + tsx@4.23.13: + dependencies: + esbuild: 0.28.2 + optionalDependencies: + fsevents: 2.3.3 + + tw-animate-css@1.4.0: {} + + tweetnacl@0.14.5: {} + + type-is@2.1.0: + dependencies: + content-type: 2.1.0 + media-typer: 1.1.1 + mime-types: 3.0.2 + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.8: + dependencies: + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript@7.0.2: + optionalDependencies: + '@typescript/typescript-aix-ppc64': 7.0.2 + '@typescript/typescript-darwin-arm64': 7.0.2 + '@typescript/typescript-darwin-x64': 7.0.2 + '@typescript/typescript-freebsd-arm64': 7.0.2 + '@typescript/typescript-freebsd-x64': 7.0.2 + '@typescript/typescript-linux-arm': 7.0.2 + '@typescript/typescript-linux-arm64': 7.0.2 + '@typescript/typescript-linux-loong64': 7.0.2 + '@typescript/typescript-linux-mips64el': 7.0.2 + '@typescript/typescript-linux-ppc64': 7.0.2 + '@typescript/typescript-linux-riscv64': 7.0.2 + '@typescript/typescript-linux-s390x': 7.0.2 + '@typescript/typescript-linux-x64': 7.0.2 + '@typescript/typescript-netbsd-arm64': 7.0.2 + '@typescript/typescript-netbsd-x64': 7.0.2 + '@typescript/typescript-openbsd-arm64': 7.0.2 + '@typescript/typescript-openbsd-x64': 7.0.2 + '@typescript/typescript-sunos-x64': 7.0.2 + '@typescript/typescript-win32-arm64': 7.0.2 + '@typescript/typescript-win32-x64': 7.0.2 + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + unc-path-regex@0.1.2: {} + + undici-types@5.26.5: {} + + undici-types@7.18.2: {} + + undici@8.10.2: {} + + unixify@1.0.0: + dependencies: + normalize-path: 2.1.1 + + unpipe@1.0.0: {} + + url-join@4.0.1: {} + + use-callback-ref@1.3.3(@types/react@19.2.18)(react@19.2.8): + dependencies: + react: 19.2.8 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.18 + + use-sidecar@1.1.3(@types/react@19.2.18)(react@19.2.8): + dependencies: + detect-node-es: 1.1.0 + react: 19.2.8 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.18 + + use-sync-external-store@1.6.0(react@19.2.8): + dependencies: + react: 19.2.8 + + util-deprecate@1.0.2: {} + + util-promisify@3.0.0: + dependencies: + object.getownpropertydescriptors: 2.1.9 + + valibot@1.4.2(typescript@7.0.2): + optionalDependencies: + typescript: 7.0.2 + + vary@1.1.2: {} + + vite-plus@0.3.0(@types/node@24.13.3)(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0): + dependencies: + '@oxc-project/types': 0.146.0 + '@oxlint/plugins': 1.79.0 + '@vitest/browser': 4.1.11(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0))(vitest@4.1.11) + '@vitest/browser-preview': 4.1.11(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0))(vitest@4.1.11) + '@vitest/expect': 4.1.11 + '@vitest/mocker': 4.1.11(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0)) + '@vitest/pretty-format': 4.1.11 + '@vitest/runner': 4.1.11 + '@vitest/snapshot': 4.1.11 + '@vitest/spy': 4.1.11 + '@vitest/utils': 4.1.11 + '@voidzero-dev/vite-plus-core': 0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0) + oxfmt: 0.64.0(vite-plus@0.3.0(@types/node@24.13.3)(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0)) + oxlint: 1.79.0(oxlint-tsgolint@7.0.2001)(vite-plus@0.3.0(@types/node@24.13.3)(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0)) + oxlint-tsgolint: 7.0.2001 + vitest: 4.1.11(@types/node@24.13.3)(@vitest/browser-preview@4.1.11)(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0)) + optionalDependencies: + '@voidzero-dev/vite-plus-darwin-arm64': 0.3.0 + '@voidzero-dev/vite-plus-darwin-x64': 0.3.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 0.3.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 0.3.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 0.3.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 0.3.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 0.3.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 0.3.0 + transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@edge-runtime/vm' + - '@opentelemetry/api' + - '@types/node' + - '@vitejs/devtools' + - '@vitest/coverage-istanbul' + - '@vitest/coverage-v8' + - '@vitest/ui' + - bufferutil + - esbuild + - happy-dom + - jiti + - jsdom + - less + - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - svelte + - terser + - tsx + - typescript + - unplugin-unused + - unrun + - utf-8-validate + - vite + - yaml + + vitest@4.1.11(@types/node@24.13.3)(@vitest/browser-preview@4.1.11)(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0)): + dependencies: + '@vitest/expect': 4.1.11 + '@vitest/mocker': 4.1.11(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0)) + '@vitest/pretty-format': 4.1.11 + '@vitest/runner': 4.1.11 + '@vitest/snapshot': 4.1.11 + '@vitest/spy': 4.1.11 + '@vitest/utils': 4.1.11 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.1.4 + pathe: 2.0.3 + picomatch: 4.0.7 + std-env: 4.2.0 + tinybench: 2.9.0 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.1 + vite: '@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0)' + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.13.3 + '@vitest/browser-preview': 4.1.11(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0))(vitest@4.1.11) + transitivePeerDependencies: + - msw + + whatwg-mimetype@4.0.0: {} + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.2.0 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.22 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.22: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + ws@8.21.3: {} + + xtend@4.0.2: {} + + y18n@5.0.8: {} + + yaml@2.9.0: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.3: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yuku-codegen@0.5.48: + dependencies: + '@yuku-toolchain/types': 0.5.43 + optionalDependencies: + '@yuku-codegen/binding-darwin-arm64': 0.5.48 + '@yuku-codegen/binding-darwin-x64': 0.5.48 + '@yuku-codegen/binding-freebsd-x64': 0.5.48 + '@yuku-codegen/binding-linux-arm-gnu': 0.5.48 + '@yuku-codegen/binding-linux-arm-musl': 0.5.48 + '@yuku-codegen/binding-linux-arm64-gnu': 0.5.48 + '@yuku-codegen/binding-linux-arm64-musl': 0.5.48 + '@yuku-codegen/binding-linux-x64-gnu': 0.5.48 + '@yuku-codegen/binding-linux-x64-musl': 0.5.48 + '@yuku-codegen/binding-win32-arm64': 0.5.48 + '@yuku-codegen/binding-win32-x64': 0.5.48 + + yuku-parser@0.5.48: + dependencies: + '@yuku-toolchain/types': 0.5.43 + optionalDependencies: + '@yuku-parser/binding-darwin-arm64': 0.5.48 + '@yuku-parser/binding-darwin-x64': 0.5.48 + '@yuku-parser/binding-freebsd-x64': 0.5.48 + '@yuku-parser/binding-linux-arm-gnu': 0.5.48 + '@yuku-parser/binding-linux-arm-musl': 0.5.48 + '@yuku-parser/binding-linux-arm64-gnu': 0.5.48 + '@yuku-parser/binding-linux-arm64-musl': 0.5.48 + '@yuku-parser/binding-linux-x64-gnu': 0.5.48 + '@yuku-parser/binding-linux-x64-musl': 0.5.48 + '@yuku-parser/binding-win32-arm64': 0.5.48 + '@yuku-parser/binding-win32-x64': 0.5.48 + + zeptomatch@2.1.0: + dependencies: + grammex: 3.1.13 + graphmatch: 1.1.1 + + zip-stream@6.0.1: + dependencies: + archiver-utils: 5.0.2 + compress-commons: 6.0.2 + readable-stream: 4.7.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..bc5c8fc --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,35 @@ +packages: + - backend + - frontend + +overrides: + vite: npm:@voidzero-dev/vite-plus-core@0.3.0 + vitest: 4.1.11 + "@prisma/config>deepmerge-ts": 8.0.0 + "prisma>mysql2": 3.24.3 + "archiver-utils>glob": 13.0.6 + +# Vite+ exposes Vite 8.2.2 under its distribution version 0.3.0. +# These tested Vite-8 consumers otherwise warn about the alias version number. +peerDependencyRules: + allowedVersions: + "vitest>vite": "0.3.0" + "@vitest/mocker>vite": "0.3.0" + "@tailwindcss/vite>vite": "0.3.0" + "@vitejs/plugin-react>vite": "0.3.0" + +allowBuilds: + protobufjs: true + ssh2: false + cpu-features: false + "@apollo/protobufjs": true + "@prisma/engines": true + core-js: false + core-js-pure: false + esbuild: true + prisma: true + +minimumReleaseAgeExclude: + - "@full-ui/headless-calendar@7.1.0" + - "@fullcalendar/core@7.1.0" + - "@fullcalendar/react@7.1.0" diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..fcf969f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,4 @@ +{ + "files": [], + "references": [{ "path": "./frontend" }, { "path": "./backend" }, { "path": "./e2e" }] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..11bd1b2 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,32 @@ +import { defineConfig } from "vite-plus"; +export default defineConfig({ + test: { include: ["backend/src/**/*.test.ts", "frontend/src/**/*.test.ts"], environment: "node" }, + lint: { + plugins: ["typescript", "react", "jsx-a11y", "import", "unicorn", "oxc"], + overrides: [ + { + files: ["frontend/src/**/*.ts", "frontend/src/**/*.tsx"], + rules: { "react/rules-of-hooks": "error", "react/exhaustive-deps": "warn" }, + }, + ], + options: { typeAware: true, typeCheck: true }, + ignorePatterns: [ + "**/generated/**", + "**/build/**", + "**/dist/**", + "**/node_modules/**", + "playwright-report/**", + "test-results/**", + ], + }, + fmt: { + ignorePatterns: [ + "**/generated/**", + "pnpm-lock.yaml", + "**/build/**", + "**/dist/**", + "playwright-report/**", + "test-results/**", + ], + }, +}); From 94410d4658c9a1fd316129d8e64c9c47a3ef167a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 09:16:03 +0200 Subject: [PATCH 02/41] Fix stale booking details after calendar moves Invalidate the moved booking's detail query alongside all calendar ranges. Cover opening details, dragging, immediately editing and preserving the moved timestamps in the real browser suite. --- e2e/calendar.spec.ts | 46 ++++++++++++++++++++++++++++++++- frontend/src/pages/calendar.tsx | 7 +++-- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/e2e/calendar.spec.ts b/e2e/calendar.spec.ts index db63f6d..eae7c36 100644 --- a/e2e/calendar.spec.ts +++ b/e2e/calendar.spec.ts @@ -1,5 +1,5 @@ import { test, expect } from "./fixtures"; -import { createBooking } from "./booking-helpers"; +import { createBooking, graphql } from "./booking-helpers"; test("calendar filtering, period navigation and language work on desktop and mobile", async ({ page, @@ -7,6 +7,50 @@ test("calendar filtering, period navigation and language work on desktop and mob const title = "E2E calendar meeting"; await createBooking(page, title); const event = page.getByRole("button").filter({ hasText: title }); + + // Prime the detail cache, then move and edit before its 20-second freshness + // window expires. Saving another field must not restore the pre-drag times. + const readBooking = async () => { + const { events } = await graphql<{ + events: { title: string; start: string; end: string }[]; + }>(page, "{ events { title start end } }"); + const booking = events.find((entry) => entry.title === title); + if (!booking) throw new Error("Calendar booking is missing"); + return booking; + }; + const originalBooking = await readBooking(); + await event.first().click(); + await expect(page.getByRole("dialog", { name: "Booking details", exact: true })).toBeVisible(); + await expect(page.getByRole("heading", { name: title, exact: true })).toBeVisible(); + await page.keyboard.press("Escape"); + await event.first().scrollIntoViewIfNeeded(); + const box = await event.first().boundingBox(); + if (!box) throw new Error("Calendar booking has no drag target"); + const movedResponse = page.waitForResponse( + (response) => + response.url().endsWith("/api/graphql/v1") && + Boolean(response.request().postData()?.includes("mutation UpdateBooking")), + ); + await page.mouse.move(box.x + box.width / 2, box.y + 10); + await page.mouse.down(); + await page.mouse.move(box.x + box.width / 2, box.y + box.height + 10, { steps: 20 }); + await page.mouse.up(); + expect((await (await movedResponse).json()).data.editEvent).toBeNull(); + const movedBooking = await readBooking(); + expect(movedBooking.start).not.toBe(originalBooking.start); + await event.first().click(); + await page.getByRole("button", { name: "Edit", exact: true }).click(); + const editStart = await page.getByLabel("Begins at", { exact: true }).inputValue(); + expect(await page.evaluate((value) => new Date(value).toISOString(), editStart)).toBe( + movedBooking.start, + ); + await page.getByLabel("Description", { exact: true }).fill("Updated after moving the booking."); + await page.getByRole("button", { name: "Save booking", exact: true }).click(); + await expect(page).toHaveURL(/\/$/); + const savedBooking = await readBooking(); + expect(savedBooking.start).toBe(movedBooking.start); + expect(savedBooking.end).toBe(movedBooking.end); + const roomFilter = page.getByRole("button", { name: "Storhubben", exact: true }); await expect(roomFilter).toHaveAttribute("aria-pressed", "true"); await roomFilter.click(); diff --git a/frontend/src/pages/calendar.tsx b/frontend/src/pages/calendar.tsx index 039d605..5ce68b0 100644 --- a/frontend/src/pages/calendar.tsx +++ b/frontend/src/pages/calendar.tsx @@ -77,8 +77,11 @@ export function CalendarPage() { }); checkMutation(editEvent, language); }, - onSuccess: async () => { - await cache.invalidateQueries({ queryKey: ["calendar"] }); + onSuccess: async (_result, { booking }) => { + await Promise.all([ + cache.invalidateQueries({ queryKey: ["calendar"] }), + cache.invalidateQueries({ queryKey: ["booking", booking.id] }), + ]); toast.success(t("Booking moved", "Bokningen flyttades")); }, }); From de1f023d1aa8564cd57be60c4708d4463c6fb253 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 09:16:03 +0200 Subject: [PATCH 03/41] Repair production Compose for the workspace deployment Build frontend and backend from the repository root, replace obsolete demo Gamma settings with required current OIDC configuration, and keep backing services private on the deferred database versions. Document explicit schema setup and existing database storage migration precautions. --- README.md | 18 ++++++ prod.docker-compose.yml | 135 ++++++++++++++-------------------------- 2 files changed, 63 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index 74dac51..f27e92a 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,24 @@ Backend port: 8080. Frontend Nginx port: 80. Configure the backend's trusted TLS reverse proxy so browser assets and API share an origin. Enable `TRUST_PROXY=1` only when exactly one trusted proxy is in front. +`prod.docker-compose.yml` builds both application images from the repository root +and connects them to BookIT's private database and Redis services. It uses an +existing Gamma issuer, not the retired bundled Gamma mock/development images. +Supply `DATABASE_URL`, `DB_PASS`, `ISSUER_BASE_URL`, `BASE_URL`, `CLIENT_ID`, +`CLIENT_SECRET`, `API_KEY`, and a strong `SESSION_SECRET` through your deployment +environment (or a protected Compose `--env-file`). For its bundled database, +`DATABASE_URL` must use host `db`, database `bookit`, user `postgres`, and the +URL-encoded `DB_PASS`. Register `${BASE_URL}/api/callback` in Gamma. The backend +binds only to host loopback for a trusted TLS reverse proxy; the other services +are not published. Set `TRUST_PROXY` for your actual proxy topology. + +Validate with `docker compose -f prod.docker-compose.yml config --quiet`, then +build with `docker compose -f prod.docker-compose.yml build`. The named +`bookit-db` volume is for new deployments: **do not switch an existing deployment +to it without mapping or migrating its current database storage first**. Back up +data and review the resolved Compose configuration before deploying. Schema +application remains an explicit release step; this file does not run it for you. + Container startup no longer applies schema changes automatically. Before a release, back up the database, review the Prisma schema diff and apply the approved schema change explicitly using `pnpm --dir backend migrate` from a release checkout diff --git a/prod.docker-compose.yml b/prod.docker-compose.yml index ce27ab0..a8bb5bc 100644 --- a/prod.docker-compose.yml +++ b/prod.docker-compose.yml @@ -1,105 +1,60 @@ -version: "3" +# Uses an existing Gamma installation. See README.md before deployment. services: backend: build: - context: backend - dockerfile: Dockerfile + context: . + dockerfile: backend/Dockerfile ports: - - 8080:8080 + - "127.0.0.1:8080:8080" depends_on: - - db - - redis + db: + condition: service_healthy + redis: + condition: service_healthy + frontend: + condition: service_started environment: - DB_USER: postgres - DB_PASS: password - DB_HOST: db - DB_PORT: 5432 - DB_NAME: bookit - - GAMMA_AUTH_URL: http://localhost:8081/api/oauth/authorize - GAMMA_TOKEN_URL: http://gamma-backend:8081/api/oauth/token - GAMMA_USER_URL: http://gamma-backend:8081/api/users/me - GAMMA_CLIENT_ID: id - GAMMA_CLIENT_SECRET: secret - GAMMA_CALLBACK_URL: http://localhost:3001/auth/callback - + DATABASE_URL: "${DATABASE_URL:?Set DATABASE_URL for the BookIT database}" + ISSUER_BASE_URL: "${ISSUER_BASE_URL:?Set the Gamma OpenID issuer URL}" + BASE_URL: "${BASE_URL:?Set the public BookIT URL}" + CLIENT_ID: "${CLIENT_ID:?Set the Gamma client ID}" + CLIENT_SECRET: "${CLIENT_SECRET:?Set the Gamma client secret}" + API_KEY: "${API_KEY:?Set the Gamma client API key}" + SESSION_SECRET: "${SESSION_SECRET:?Set a strong random session secret}" + FRONTEND_URL: http://frontend:80 REDIS_HOST: redis - REDIS_PORT: 6379 - REDIS_PASS: "" + REDIS_PORT: "6379" + PORT: "8080" + TZ: Europe/Stockholm + TRUST_PROXY: "${TRUST_PROXY:-0}" - SESSION_SECRET: secret - GRAPHIQL: "true" - MOCK: "true" - ADMIN_AUTHORITY: bookit-admin - PORT: 8080 - TZ: "Europe/Stockholm" + frontend: + build: + context: . + dockerfile: frontend/Dockerfile db: - image: postgres:12 + image: postgres:12.22-alpine environment: POSTGRES_DB: bookit POSTGRES_USER: postgres - POSTGRES_PASSWORD: password - TZ: "Europe/Stockholm" - ports: - - 5432:5432 + POSTGRES_PASSWORD: "${DB_PASS:?Set the BookIT database password}" + TZ: Europe/Stockholm + volumes: + - bookit-db:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres -d bookit"] + interval: 5s + timeout: 5s + retries: 12 redis: - image: redis:5.0 - ports: - - 6379:6379 - - ################## Gamma ################## - gamma-frontend: - image: cthit/gamma-frontend:development - environment: - HTTP_PROXY: http://gamma-backend:8081 - depends_on: - - gamma-backend - ports: - - 3000:3000 - - gamma-backend: - image: cthit/gamma-backend:development - environment: - # Default admin user name = admin - # Default admin password = password - - DB_USER: user - DB_PASSWORD: password - DB_HOST: gamma-db - DB_PORT: 5432 - DB_NAME: postgres - - REDIS_HOST: gamma-redis - REDIS_PASSWORD: "" - REDIS_PORT: 6379 - - DEFAULT_CLIENT_NAME: demo - DEFAULT_CLIENT_ID: id - DEFAULT_CLIENT_SECRET: secret - DEFAULT_REDIRECT_URI: http://localhost:3001/auth/callback - DEFAULT_API_KEY: key - - SERVER_PORT: 8081 - SUCCESSFUL_LOGIN: http://localhost:3000 - CORS_ALLOWED_ORIGIN: http://localhost:3000 - BACKEND_URI: http://localhost:8081/api/ - PRODUCTION: "false" - COOKIE_DOMAIN: localhost - IS_MOCKING: "true" - depends_on: - - gamma-redis - - gamma-db - ports: - - 8081:8081 - - gamma-redis: - image: redis:5.0 - - gamma-db: - image: postgres:10 - environment: - POSTGRES_USER: user - POSTGRES_DB: postgres - POSTGRES_PASSWORD: password + image: redis:5.0.14-alpine + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 5s + retries: 12 + +volumes: + bookit-db: From a2511fefa3e82823c8e3fffeebd6fa29788cfbdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 09:41:57 +0200 Subject: [PATCH 04/41] Make E2E scenarios focused and prefer accessible role selectors Split four packed flows into ten independent tests while sharing the existing worker environment. Separate setup, actions and assertions with whitespace, format all E2E files with Vite+, and document role-first selectors with date/time label fallbacks. Verified ten browser tests, the retained mobile day-view regression, 45 unit/API tests, strict TypeScript and formatting/lint checks. --- e2e/README.md | 19 ++++++++ e2e/admin-rules.spec.ts | 52 +++++++++++++++++++--- e2e/booking-helpers.ts | 26 ++++++++--- e2e/booking-lifecycle.spec.ts | 24 ++++++++-- e2e/calendar.spec.ts | 84 ++++++++++++++++++++++++++--------- e2e/compose.ts | 37 +++++++++++++++ e2e/dev.ts | 5 +++ e2e/fixtures.ts | 7 +++ e2e/permissions.spec.ts | 25 ++++++++++- 9 files changed, 242 insertions(+), 37 deletions(-) diff --git a/e2e/README.md b/e2e/README.md index 6533a02..5614f6e 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -11,6 +11,25 @@ must be running. A worker shares the container environment, while each test gets a fresh browser context and cleared BookIT bookings/rules. Run with one worker to keep the suite's resource usage predictable. +The four spec files contain focused, independent tests rather than one long test +per file. Booking lifecycle and concurrency, calendar navigation and drag/edit +consistency, language persistence, mobile layout, rule validation/lifecycle, and +UI/API permissions have separate results. Each test gets its own login and data +reset; the services stay shared for the worker. + +Prefer `getByRole` with an accessible name and scope it to the relevant dialog or +navigation when needed. Use `getByLabel` for native date/time inputs, which have +no useful implicit role; do not add artificial roles just to satisfy a selector. + +E2E source uses the same Vite+ formatter as the application: + +```sh +pnpm exec vp fmt e2e playwright.config.ts +``` + +Use `pnpm format` for the whole repository. `pnpm check` checks E2E formatting and +lint in CI as well. + For the manual smoke test, run `pnpm e2e:dev` and open the printed BookIT URL. The command prints synthetic login credentials and keeps the environment alive. Ctrl+C stops application processes and removes the test containers and network. diff --git a/e2e/admin-rules.spec.ts b/e2e/admin-rules.spec.ts index ca64708..24a8371 100644 --- a/e2e/admin-rules.spec.ts +++ b/e2e/admin-rules.spec.ts @@ -3,39 +3,74 @@ import { bookingDate } from "./booking-helpers"; test.use({ role: "admin" }); -test("an administrator creates, views and deletes a recurring booking rule", async ({ page }) => { +test("a new rule has the original defaults and requires a selected weekday", async ({ page }) => { await page.getByRole("navigation").getByRole("link", { name: "Rules", exact: true }).click(); await expect(page.getByRole("heading", { name: "Rules", exact: true })).toBeVisible(); + await page.getByRole("button", { name: "New rule", exact: true }).click(); const form = page.getByRole("dialog", { name: "New rule", exact: true }); - await expect(form.getByLabel("Priority", { exact: true })).toHaveValue("10"); - await expect(form.getByLabel("Availability", { exact: true })).toHaveValue("true"); + + await expect(form.getByRole("spinbutton", { name: "Priority", exact: true })).toHaveValue("10"); + await expect(form.getByRole("combobox", { name: "Availability", exact: true })).toHaveValue( + "true", + ); await expect(form.getByLabel("End date", { exact: true })).toHaveValue("2040-12-31"); await expect(form.getByRole("checkbox", { checked: true })).toHaveCount(0); - await form.getByLabel("Title", { exact: true }).fill("E2E room maintenance"); - await form.getByLabel("Priority", { exact: true }).fill("2"); - await form.getByLabel("Availability", { exact: true }).selectOption("false"); + + await form.getByRole("textbox", { name: "Title", exact: true }).fill("E2E room maintenance"); + await form.getByRole("spinbutton", { name: "Priority", exact: true }).fill("2"); + await form.getByRole("combobox", { name: "Availability", exact: true }).selectOption("false"); await form.getByLabel("Start date", { exact: true }).fill(await bookingDate(page)); await form.getByLabel("End date", { exact: true }).fill("2040-12-31"); await form.getByLabel("Start time", { exact: true }).fill("12:00"); await form.getByLabel("End time", { exact: true }).fill("13:00"); await form.getByRole("checkbox", { name: "Storhubben", exact: true }).check(); - await form.getByLabel("Description", { exact: true }).fill("The room is closed for maintenance."); + await form + .getByRole("textbox", { name: "Description", exact: true }) + .fill("The room is closed for maintenance."); + await form.getByRole("button", { name: "Save rule", exact: true }).click(); await expect(form.getByRole("alert")).toHaveText("Select rooms and at least one weekday."); +}); + +test("an administrator creates, views and deletes a recurring booking rule", async ({ page }) => { + await page.getByRole("navigation").getByRole("link", { name: "Rules", exact: true }).click(); + await expect(page.getByRole("heading", { name: "Rules", exact: true })).toBeVisible(); + + await page.getByRole("button", { name: "New rule", exact: true }).click(); + const form = page.getByRole("dialog", { name: "New rule", exact: true }); + + await form.getByRole("textbox", { name: "Title", exact: true }).fill("E2E room maintenance"); + await form.getByRole("spinbutton", { name: "Priority", exact: true }).fill("2"); + await form.getByRole("combobox", { name: "Availability", exact: true }).selectOption("false"); + await form.getByLabel("Start date", { exact: true }).fill(await bookingDate(page)); + await form.getByLabel("End date", { exact: true }).fill("2040-12-31"); + await form.getByLabel("Start time", { exact: true }).fill("12:00"); + await form.getByLabel("End time", { exact: true }).fill("13:00"); + await form.getByRole("checkbox", { name: "Storhubben", exact: true }).check(); + await form + .getByRole("textbox", { name: "Description", exact: true }) + .fill("The room is closed for maintenance."); + for (const weekday of ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]) { await form.getByRole("checkbox", { name: weekday, exact: true }).check(); } + await form.getByRole("button", { name: "Save rule", exact: true }).click(); await expect(form).not.toBeVisible(); + await page.reload(); + const row = page.getByRole("row").filter({ hasText: "E2E room maintenance" }); + await expect(row).toContainText("Storhubben"); await expect(row).toContainText("12:00–13:00"); await expect(row).toContainText("Blocked"); await expect(row).toContainText("Mon · Tue · Wed · Thu · Fri · Sat · Sun"); + await row.getByRole("button", { name: "Details E2E room maintenance", exact: true }).click(); const details = page.getByRole("dialog", { name: "Rule details", exact: true }); + await expect( details.getByText("The room is closed for maintenance.", { exact: true }), ).toBeVisible(); @@ -45,14 +80,17 @@ test("an administrator creates, views and deletes a recurring booking rule", asy await expect( details.getByText("Updated", { exact: true }).locator("..").locator("dd"), ).toContainText(/\d{4}/); + await details.press("Escape"); await expect(details).not.toBeVisible(); + await row.getByRole("button", { name: "Delete E2E room maintenance", exact: true }).click(); await page .getByRole("dialog", { name: "Delete rule?" }) .getByRole("button", { name: "Confirm deletion", exact: true }) .click(); await expect(row).toHaveCount(0); + await page.reload(); await expect(page.getByText("No booking rules yet.", { exact: true })).toBeVisible(); }); diff --git a/e2e/booking-helpers.ts b/e2e/booking-helpers.ts index fb7c434..ceb5a21 100644 --- a/e2e/booking-helpers.ts +++ b/e2e/booking-helpers.ts @@ -4,6 +4,7 @@ export async function bookingDate(page: Page): Promise<string> { // Match the browser's date, including CI's configured timezone. return page.evaluate(() => { const now = new Date(); + return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`; }); } @@ -16,24 +17,32 @@ export async function acceptConditions(page: Page) { export async function fillBooking(page: Page, title: string) { const date = await bookingDate(page); - await page.getByLabel("Title", { exact: true }).fill(title); - await page.getByLabel("Phone number", { exact: true }).fill("0701234567"); - await expect(page.getByLabel("Booking as", { exact: true })).toHaveValue(""); + + await page.getByRole("textbox", { name: "Title", exact: true }).fill(title); + await page.getByRole("textbox", { name: "Phone number", exact: true }).fill("0701234567"); + + await expect(page.getByRole("combobox", { name: "Booking as", exact: true })).toHaveValue(""); await expect(page.getByRole("checkbox", { name: "Storhubben", exact: true })).toBeChecked(); - await page.getByLabel("Booking as", { exact: true }).selectOption("digit"); + + await page.getByRole("combobox", { name: "Booking as", exact: true }).selectOption("digit"); await page.getByRole("checkbox", { name: "Storhubben", exact: true }).check(); + await page.getByLabel("Begins at", { exact: true }).fill(`${date}T12:00`); await page.getByLabel("Ends at", { exact: true }).fill(`${date}T13:00`); + await page - .getByLabel("Description", { exact: true }) + .getByRole("textbox", { name: "Description", exact: true }) .fill("Planning together in our shared room."); + await acceptConditions(page); } export async function createBooking(page: Page, title: string) { await page.goto("/new-event"); await fillBooking(page, title); + await page.getByRole("button", { name: "Save booking", exact: true }).click(); + await expect(page).toHaveURL(/\/$/); await expect(page.getByRole("button").filter({ hasText: title }).first()).toBeVisible(); } @@ -42,14 +51,18 @@ export async function openBooking(page: Page, title: string): Promise<string> { await page.getByRole("button").filter({ hasText: title }).first().click(); await expect(page.getByRole("dialog", { name: "Booking details", exact: true })).toBeVisible(); await expect(page.getByRole("heading", { name: title, exact: true })).toBeVisible(); + const result = await graphql<{ events: { id: string; title: string }[] }>( page, "{ events { id title } }", ); + const id = result.events.find((event) => event.title === title)?.id; if (!id) throw new Error("Booking details did not include a booking id"); + await page.goto(`/bookings/${id}`); await expect(page.getByRole("heading", { name: title, exact: true })).toBeVisible(); + return id; } @@ -62,9 +75,12 @@ export async function graphql<T>( headers: { Origin: new URL(page.url()).origin }, data: { query, variables }, }); + expect(response.status()).toBe(200); + const body = (await response.json()) as { data?: T; errors?: unknown[] }; expect(body.errors).toBeUndefined(); if (!body.data) throw new Error("GraphQL response has no data"); + return body.data; } diff --git a/e2e/booking-lifecycle.spec.ts b/e2e/booking-lifecycle.spec.ts index 72ac21e..3149f0b 100644 --- a/e2e/booking-lifecycle.spec.ts +++ b/e2e/booking-lifecycle.spec.ts @@ -13,21 +13,29 @@ import type { test("a group member creates, edits and deletes a persisted booking", async ({ page }) => { const title = "E2E digIT planning"; + await createBooking(page, title); const id = await openBooking(page, title); + await expect( page.getByText("Planning together in our shared room.", { exact: true }), ).toBeVisible(); + await page.reload(); await expect(page.getByRole("heading", { name: title, exact: true })).toBeVisible(); await page.getByRole("button", { name: "Edit", exact: true }).click(); - await expect(page.getByLabel("Phone number", { exact: true })).toHaveValue("0701234567"); - await page.getByLabel("Title", { exact: true }).fill(`${title} updated`); - await page.getByLabel("Description", { exact: true }).fill("Agenda updated by a group member."); + await expect(page.getByRole("textbox", { name: "Phone number", exact: true })).toHaveValue( + "0701234567", + ); + await page.getByRole("textbox", { name: "Title", exact: true }).fill(`${title} updated`); + await page + .getByRole("textbox", { name: "Description", exact: true }) + .fill("Agenda updated by a group member."); await acceptConditions(page); await page.getByRole("button", { name: "Save booking", exact: true }).click(); await expect(page).toHaveURL(/\/$/); + await openBooking(page, `${title} updated`); await expect(page).toHaveURL((url) => url.pathname === `/bookings/${id}`); await expect(page.getByText("Agenda updated by a group member.", { exact: true })).toBeVisible(); @@ -36,17 +44,22 @@ test("a group member creates, edits and deletes a persisted booking", async ({ p const dialog = page.getByRole("dialog", { name: "Delete booking?" }); await dialog.getByRole("button", { name: "Cancel", exact: true }).click(); await expect(page.getByRole("heading", { name: `${title} updated`, exact: true })).toBeVisible(); + await page.getByRole("button", { name: "Delete", exact: true }).click(); await dialog.getByRole("button", { name: "Confirm deletion", exact: true }).click(); await expect(page).toHaveURL(/\/$/); await expect(page.getByRole("button").filter({ hasText: title })).toHaveCount(0); + await page.goto(`/bookings/${id}`); await expect(page.getByRole("alert")).toHaveText("Booking not found."); +}); +test("simultaneous requests cannot reserve the same room and time twice", async ({ page }) => { // These requests race over HTTP against the real PostgreSQL-backed service. // Checking availability and inserting must form one atomic operation. const date = await bookingDate(page); const titles = ["E2E concurrent booking A", "E2E concurrent booking B"]; + const outcomes = await Promise.all( titles.map((concurrentTitle) => graphql<CreateBookingMutation>( @@ -66,24 +79,29 @@ test("a group member creates, edits and deletes a persisted booking", async ({ p ), ), ); + expect(outcomes.filter((outcome) => outcome.createEvent === null)).toHaveLength(1); expect( outcomes.filter((outcome) => outcome.createEvent?.en === "The time slot is already taken"), ).toHaveLength(1); + const persisted = await graphql<{ events: { id: string; title: string }[] }>( page, "{ events { id title } }", ); const concurrentBookings = persisted.events.filter((booking) => titles.includes(booking.title)); expect(concurrentBookings).toHaveLength(1); + const winner = concurrentBookings[0]; if (!winner) throw new Error("The successful concurrent booking was not persisted"); + const removed = await graphql<DeleteBookingMutation>( page, "mutation($id: String!) { deleteEvent(id: $id) { en sv } }", { id: winner.id }, ); expect(removed.deleteEvent).toBeNull(); + const afterCleanup = await graphql<{ events: { id: string; title: string }[] }>( page, "{ events { id title } }", diff --git a/e2e/calendar.spec.ts b/e2e/calendar.spec.ts index eae7c36..281ac87 100644 --- a/e2e/calendar.spec.ts +++ b/e2e/calendar.spec.ts @@ -1,11 +1,38 @@ import { test, expect } from "./fixtures"; import { createBooking, graphql } from "./booking-helpers"; -test("calendar filtering, period navigation and language work on desktop and mobile", async ({ - page, -}) => { +test("room filters and period navigation show the expected bookings", async ({ page }) => { const title = "E2E calendar meeting"; + + await createBooking(page, title); + + const event = page.getByRole("button").filter({ hasText: title }); + const roomFilter = page.getByRole("button", { name: "Storhubben", exact: true }); + await expect(roomFilter).toHaveAttribute("aria-pressed", "true"); + await roomFilter.click(); + await expect(roomFilter).toHaveAttribute("aria-pressed", "false"); + await expect(event).toHaveCount(0); + + await roomFilter.click(); + await expect(event.first()).toBeVisible(); + + const period = page.getByRole("heading", { level: 3, name: /\d{4}/ }); + const originalPeriod = await period.innerText(); + + await page.getByRole("button", { name: /next/i }).click(); + await expect(period).not.toHaveText(originalPeriod); + await expect(event).toHaveCount(0); + + await page.getByRole("button", { name: /previous/i }).click(); + await expect(period).toHaveText(originalPeriod); + await expect(event.first()).toBeVisible(); +}); + +test("editing immediately after a calendar drag preserves the moved times", async ({ page }) => { + const title = "E2E moved booking"; + await createBooking(page, title); + const event = page.getByRole("button").filter({ hasText: title }); // Prime the detail cache, then move and edit before its 20-second freshness @@ -14,77 +41,92 @@ test("calendar filtering, period navigation and language work on desktop and mob const { events } = await graphql<{ events: { title: string; start: string; end: string }[]; }>(page, "{ events { title start end } }"); + const booking = events.find((entry) => entry.title === title); if (!booking) throw new Error("Calendar booking is missing"); + return booking; }; + const originalBooking = await readBooking(); await event.first().click(); await expect(page.getByRole("dialog", { name: "Booking details", exact: true })).toBeVisible(); await expect(page.getByRole("heading", { name: title, exact: true })).toBeVisible(); await page.keyboard.press("Escape"); + await event.first().scrollIntoViewIfNeeded(); const box = await event.first().boundingBox(); if (!box) throw new Error("Calendar booking has no drag target"); + const movedResponse = page.waitForResponse( (response) => response.url().endsWith("/api/graphql/v1") && Boolean(response.request().postData()?.includes("mutation UpdateBooking")), ); + await page.mouse.move(box.x + box.width / 2, box.y + 10); await page.mouse.down(); await page.mouse.move(box.x + box.width / 2, box.y + box.height + 10, { steps: 20 }); await page.mouse.up(); expect((await (await movedResponse).json()).data.editEvent).toBeNull(); + const movedBooking = await readBooking(); expect(movedBooking.start).not.toBe(originalBooking.start); + await event.first().click(); await page.getByRole("button", { name: "Edit", exact: true }).click(); + const editStart = await page.getByLabel("Begins at", { exact: true }).inputValue(); expect(await page.evaluate((value) => new Date(value).toISOString(), editStart)).toBe( movedBooking.start, ); - await page.getByLabel("Description", { exact: true }).fill("Updated after moving the booking."); + + await page + .getByRole("textbox", { name: "Description", exact: true }) + .fill("Updated after moving the booking."); await page.getByRole("button", { name: "Save booking", exact: true }).click(); await expect(page).toHaveURL(/\/$/); + const savedBooking = await readBooking(); expect(savedBooking.start).toBe(movedBooking.start); expect(savedBooking.end).toBe(movedBooking.end); +}); - const roomFilter = page.getByRole("button", { name: "Storhubben", exact: true }); - await expect(roomFilter).toHaveAttribute("aria-pressed", "true"); - await roomFilter.click(); - await expect(roomFilter).toHaveAttribute("aria-pressed", "false"); - await expect(event).toHaveCount(0); - await roomFilter.click(); - await expect(event.first()).toBeVisible(); - - const period = page.getByRole("heading", { level: 3, name: /\d{4}/ }); - const originalPeriod = await period.innerText(); - await page.getByRole("button", { name: /next/i }).click(); - await expect(period).not.toHaveText(originalPeriod); - await expect(event).toHaveCount(0); - await page.getByRole("button", { name: /previous/i }).click(); - await expect(period).toHaveText(originalPeriod); - await expect(event.first()).toBeVisible(); +test("the selected language survives a reload", async ({ page }) => { + await page.goto("/"); await page.getByRole("button", { name: "Switch to Swedish", exact: true }).click(); await expect(page.getByRole("heading", { name: "Kalender", exact: true })).toBeAttached(); + await page.reload(); await expect(page.getByRole("button", { name: "Byt till engelska", exact: true })).toBeVisible(); await page.getByRole("button", { name: "Byt till engelska", exact: true }).click(); +}); + +test("the mobile calendar shows bookings without horizontal overflow", async ({ page }) => { + const title = "E2E mobile booking"; + + await createBooking(page, title); + + const event = page.getByRole("button").filter({ hasText: title }); + const period = page.getByRole("heading", { level: 3, name: /\d{4}/ }); + const desktopPeriod = await period.innerText(); await page.setViewportSize({ width: 390, height: 844 }); await page.reload(); await expect(page.getByRole("heading", { name: "Calendar", exact: true })).toBeAttached(); - await expect(period).not.toHaveText(originalPeriod); + await expect(period).not.toHaveText(desktopPeriod); + const mobilePeriod = await period.innerText(); + await expect(page.getByRole("grid", { name: mobilePeriod, exact: true })).toBeVisible(); await expect(page.getByRole("tab", { name: /list/i })).toHaveAttribute("aria-selected", "false"); await expect(event.first()).toBeVisible(); await expect(page.getByRole("link", { name: "New booking", exact: true })).toBeVisible(); + const overflow = await page.evaluate( () => document.documentElement.scrollWidth - window.innerWidth, ); + expect(overflow).toBeLessThanOrEqual(1); }); diff --git a/e2e/compose.ts b/e2e/compose.ts index f117084..e471a50 100644 --- a/e2e/compose.ts +++ b/e2e/compose.ts @@ -40,6 +40,7 @@ export const users = { groups: [], }, } as const; + export type UserRole = keyof typeof users; export const testPassword = "password1337"; @@ -88,9 +89,11 @@ async function unusedPort(): Promise<number> { async function stopProcess(child: ChildProcess): Promise<void> { if (!child.pid || child.exitCode !== null || child.signalCode !== null) return; + const exited = new Promise<void>((resolve) => child.once("exit", () => resolve())); process.kill(-child.pid, "SIGTERM"); await Promise.race([exited, delay(10_000)]); + if (child.exitCode === null && child.signalCode === null) { process.kill(-child.pid, "SIGKILL"); await exited; @@ -99,6 +102,7 @@ async function stopProcess(child: ChildProcess): Promise<void> { async function provisionClient(browser: Browser, gammaUrl: string, appUrl: string) { const context = await browser.newContext(); + try { const page = await context.newPage(); page.setDefaultTimeout(30_000); @@ -107,6 +111,7 @@ async function provisionClient(browser: Browser, gammaUrl: string, appUrl: strin await page.locator('[name="password"]').fill(testPassword); await page.getByRole("button", { name: "Login", exact: true }).click(); await expect(page.getByText("Hey, admin!")).toBeVisible(); + await page.goto(`${gammaUrl}/clients/create`); await page.locator('[name="prettyName"]').fill("BookIT isolated E2E"); await page.locator('[name="svDescription"]').fill("Lokalt integrationstest"); @@ -115,6 +120,7 @@ async function provisionClient(browser: Browser, gammaUrl: string, appUrl: strin await page.locator('[name="generateApiKey"]').check(); await page.getByRole("button", { name: "Create", exact: true }).click(); await expect(page.getByText("Client details", { exact: true })).toBeVisible(); + const clientId = (await page.locator('li:has-text("Client id:") span').innerText()).trim(); const credentials = page .locator("article") @@ -125,9 +131,11 @@ async function provisionClient(browser: Browser, gammaUrl: string, appUrl: strin .filter({ hasText: "Authorization: pre-shared" }) .innerText(); const apiKey = authorization.replace("Authorization: pre-shared", "").trim(); + if (!clientId || !clientSecret || !/^[\w-]+:\S+$/.test(apiKey)) { throw new Error("Gamma did not return complete OAuth and CLIENT API credentials"); } + // BookIT admin means this client's authority, not Gamma's global admin flag. await page.locator('[name="authority"]').fill("admin"); await page.getByRole("button", { name: "Add user authority", exact: true }).click(); @@ -136,6 +144,7 @@ async function provisionClient(browser: Browser, gammaUrl: string, appUrl: strin await expect( page.locator("article").filter({ has: page.locator("header", { hasText: /^\s*admin\s*$/ }) }), ).toBeVisible(); + return { clientId, clientSecret, apiKey }; } finally { await context.close(); @@ -149,13 +158,16 @@ export async function loginAs(page: Page, environment: Environment, role: UserRo await page.locator('[name="username"]').fill(users[role].cid); await page.locator('[name="password"]').fill(testPassword); await page.getByRole("button", { name: "Login", exact: true }).click(); + await Promise.race([ page.waitForURL((url) => url.origin === environment.appUrl), page.getByRole("button", { name: "Authorize", exact: true }).waitFor(), ]); + if (new URL(page.url()).origin === environment.gammaUrl) { await page.getByRole("button", { name: "Authorize", exact: true }).click(); } + await page.waitForURL( (url) => url.origin === environment.appUrl && url.pathname !== "/api/callback", ); @@ -167,6 +179,7 @@ export async function compose(browser: Browser): Promise<Environment> { const processes: ChildProcess[] = []; const logs: Record<string, string> = {}; let stopped = false; + const record = (name: string, chunk: Buffer | string) => { // Gamma's development bootstrap prints synthetic credentials. Keep them out of CI artifacts. const text = String(chunk) @@ -174,15 +187,18 @@ export async function compose(browser: Browser): Promise<Environment> { .replace(/(and code:)\s*\S+/g, "$1 [REDACTED]"); logs[name] = ((logs[name] ?? "") + text).slice(-100_000); }; + const track = async <T extends StartedTestContainer>(pending: Promise<T>): Promise<T> => { const container = await pending; containers.push(container); return container; }; + const stop = async () => { if (stopped) return; stopped = true; const failures: unknown[] = []; + for (const child of processes.toReversed()) { try { await stopProcess(child); @@ -190,6 +206,7 @@ export async function compose(browser: Browser): Promise<Environment> { failures.push(error); } } + for (const container of containers.toReversed()) { try { await container.stop(); @@ -197,13 +214,16 @@ export async function compose(browser: Browser): Promise<Environment> { failures.push(error); } } + try { await network.stop(); } catch (error) { failures.push(error); } + if (failures.length) throw new AggregateError(failures, "E2E environment cleanup failed"); }; + const startProcess = (name: string, args: string[], env: Record<string, string>) => { const child = spawn("pnpm", args, { cwd: root, @@ -211,6 +231,7 @@ export async function compose(browser: Browser): Promise<Environment> { detached: true, stdio: ["ignore", "pipe", "pipe"], }); + processes.push(child); child.stdout?.on("data", (chunk: Buffer) => record(name, chunk)); child.stderr?.on("data", (chunk: Buffer) => record(name, chunk)); @@ -219,8 +240,10 @@ export async function compose(browser: Browser): Promise<Environment> { if (code !== null && code !== 0 && !stopped) console.error(`[${name}] exited with code ${code}\n${logs[name] ?? ""}`); }); + return child; }; + try { console.log("Starting isolated PostgreSQL, Redis and Gamma containers..."); const gammaDb = await track( @@ -232,6 +255,7 @@ export async function compose(browser: Browser): Promise<Environment> { .withPassword("gamma_test") .start(), ); + await track( new GenericContainer(images.gammaRedis) .withNetwork(network) @@ -239,6 +263,7 @@ export async function compose(browser: Browser): Promise<Environment> { .withWaitStrategy(Wait.forLogMessage("Ready to accept connections")) .start(), ); + const bookitDb: StartedPostgreSqlContainer = await track( new PostgreSqlContainer(images.bookitPostgres) .withNetwork(network) @@ -248,6 +273,7 @@ export async function compose(browser: Browser): Promise<Environment> { .withPassword("bookit_test") .start(), ); + const bookitRedis = await track( new GenericContainer(images.bookitRedis) .withNetwork(network) @@ -256,11 +282,13 @@ export async function compose(browser: Browser): Promise<Environment> { .withWaitStrategy(Wait.forLogMessage("Ready to accept connections")) .start(), ); + const gammaPort = await unusedPort(); const gammaUrl = `http://localhost:${gammaPort}`; const appPort = await unusedPort(); const appUrl = `http://localhost:${appPort}`; const frontendPort = await unusedPort(); + await track( new GenericContainer(images.gamma) .withPlatform("linux/amd64") @@ -295,8 +323,10 @@ export async function compose(browser: Browser): Promise<Environment> { .withStartupTimeout(240_000) .start(), ); + console.log("Provisioning BookIT OAuth client and roles through Gamma..."); const client = await provisionClient(browser, gammaUrl, appUrl); + const env = { NODE_ENV: "test", TZ: "Europe/Stockholm", @@ -315,6 +345,7 @@ export async function compose(browser: Browser): Promise<Environment> { CLIENT_SECRET: client.clientSecret, API_KEY: client.apiKey, }; + console.log("Generating Prisma client and initializing the isolated BookIT schema..."); for (const command of [["generate"], ["db", "push"]]) { const migrate = startProcess( @@ -331,6 +362,7 @@ export async function compose(browser: Browser): Promise<Environment> { ); }); } + const frontend = startProcess( "frontend", [ @@ -346,6 +378,7 @@ export async function compose(browser: Browser): Promise<Environment> { env, ); const backend = startProcess("backend", ["--dir", "backend", "start"], env); + await expect .poll( async () => { @@ -370,11 +403,13 @@ export async function compose(browser: Browser): Promise<Environment> { { timeout: 120_000, intervals: [500, 1000] }, ) .toBe(true); + return { appUrl, gammaUrl, logs, stop, + async resetBookings() { const result = await bookitDb.exec([ "psql", @@ -387,6 +422,7 @@ export async function compose(browser: Browser): Promise<Environment> { "--command", "TRUNCATE TABLE event, rule;", ]); + if (result.exitCode !== 0) throw new Error(`Could not reset isolated BookIT database: ${result.output}`); }, @@ -394,6 +430,7 @@ export async function compose(browser: Browser): Promise<Environment> { } catch (error) { for (const [name, log] of Object.entries(logs)) console.error(`[${name}]\n${log.slice(-8_000)}`); + await stop(); throw error; } diff --git a/e2e/dev.ts b/e2e/dev.ts index 881dadd..f89c3de 100644 --- a/e2e/dev.ts +++ b/e2e/dev.ts @@ -5,24 +5,29 @@ async function main() { const browser = await chromium.launch(); let stopEnvironment: (() => Promise<void>) | undefined; let stopping = false; + async function stop() { if (stopping) return; stopping = true; + try { await stopEnvironment?.(); } finally { await browser.close(); } } + process.once("SIGINT", () => { void stop(); }); process.once("SIGTERM", () => { void stop(); }); + try { const environment = await compose(browser); stopEnvironment = () => environment.stop(); + console.log(`BookIT smoke environment: ${environment.appUrl}`); console.log(`Gamma: ${environment.gammaUrl}`); console.log( diff --git a/e2e/fixtures.ts b/e2e/fixtures.ts index c15b534..6c464cd 100644 --- a/e2e/fixtures.ts +++ b/e2e/fixtures.ts @@ -11,6 +11,7 @@ export const test = base.extend<TestFixtures, { environment: Environment }>({ environment: [ async ({ browser }, use) => { const environment = await compose(browser); + try { await use(environment); } finally { @@ -19,14 +20,18 @@ export const test = base.extend<TestFixtures, { environment: Environment }>({ }, { scope: "worker", timeout: 480_000 }, ], + role: ["member", { option: true }], authenticate: [true, { option: true }], + baseURL: async ({ environment }, use) => { await use(environment.appUrl); }, + isolatedBookings: [ async ({ environment }, use, testInfo) => { await environment.resetBookings(); + try { await use(); } finally { @@ -39,8 +44,10 @@ export const test = base.extend<TestFixtures, { environment: Environment }>({ }, { auto: true }, ], + page: async ({ page, environment, role, authenticate }, use) => { if (authenticate) await loginAs(page, environment, role); + await use(page); }, }); diff --git a/e2e/permissions.spec.ts b/e2e/permissions.spec.ts index 31c2189..1c86bac 100644 --- a/e2e/permissions.spec.ts +++ b/e2e/permissions.spec.ts @@ -8,14 +8,16 @@ import type { BookingDetailQuery, } from "../frontend/src/generated/graphql"; -test("Gamma membership controls the UI and the API rejects a forged group booking", async ({ +test("an outsider can view bookings but cannot access editing or administration", async ({ page, environment, }) => { await createBooking(page, "E2E protected group booking"); const id = await openBooking(page, "E2E protected group booking"); await loginAs(page, environment, "outsider"); + const identity = await graphql<CurrentUserQuery>(page, "{ user { sub cid groups is_admin } }"); + expect(identity.user?.cid).toBe("bookguest"); expect(identity.user?.groups).toEqual([]); expect(identity.user?.is_admin).toBe(false); @@ -25,22 +27,38 @@ test("Gamma membership controls the UI and the API rejects a forged group bookin "You need membership in an active group to make a booking.", ); await expect(page.getByRole("button", { name: "Save booking", exact: true })).toHaveCount(0); + await page.goto("/rules"); await expect(page.getByRole("heading", { name: "Rules", exact: true })).toBeVisible(); await expect(page.getByRole("button", { name: "New rule", exact: true })).toHaveCount(0); + await page.goto(`/bookings/${id}`); await expect( page.getByRole("heading", { name: "E2E protected group booking", exact: true }), ).toBeVisible(); await expect(page.getByRole("button", { name: "Edit", exact: true })).toHaveCount(0); await expect(page.getByRole("button", { name: "Delete", exact: true })).toHaveCount(0); +}); + +test("the API rejects unauthorized deletion, group booking and rule creation", async ({ + page, + environment, +}) => { + const title = "E2E protected group booking"; + + await createBooking(page, title); + const id = await openBooking(page, title); + + await loginAs(page, environment, "outsider"); const deletion = await graphql<DeleteBookingMutation>( page, "mutation($id: String!) { deleteEvent(id: $id) { en sv } }", { id }, ); + expect(deletion.deleteEvent?.en).toBe("You may not delete this event"); + const date = await bookingDate(page); const forged = await graphql<CreateBookingMutation>( page, @@ -57,7 +75,9 @@ test("Gamma membership controls the UI and the API rejects a forged group bookin }, }, ); + expect(forged.createEvent?.en).toBe("Booking group not specified"); + const rule = await graphql<CreateRuleMutation>( page, "mutation($rule: InputRule!) { createRule(rule: $rule) { en sv } }", @@ -75,11 +95,14 @@ test("Gamma membership controls the UI and the API rejects a forged group bookin }, }, ); + expect(rule.createRule?.en).toBe("You do not have permission to create rules"); + const unchanged = await graphql<BookingDetailQuery>( page, "query($id: String!) { event(id: $id) { id title } }", { id }, ); + expect(unchanged.event?.title).toBe("E2E protected group booking"); }); From ae0d40662cdc583e4adba31f708eebabbc361b93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 10:07:07 +0200 Subject: [PATCH 05/41] Improve date and time field accessibility with keyboard-friendly controls --- e2e/README.md | 12 +- e2e/accessibility.spec.ts | 80 ++++++++ e2e/admin-rules.spec.ts | 70 ++++++- e2e/booking-helpers.ts | 13 +- e2e/calendar.spec.ts | 18 +- e2e/date-time-helpers.ts | 34 ++++ frontend/package.json | 2 + frontend/src/components/booking-form.tsx | 11 +- frontend/src/components/date-time-fields.tsx | 197 +++++++++++++++++++ frontend/src/pages/rules.tsx | 56 ++---- pnpm-lock.yaml | 101 ++++++++++ 11 files changed, 532 insertions(+), 62 deletions(-) create mode 100644 e2e/accessibility.spec.ts create mode 100644 e2e/date-time-helpers.ts create mode 100644 frontend/src/components/date-time-fields.tsx diff --git a/e2e/README.md b/e2e/README.md index 5614f6e..a28fbba 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -11,15 +11,21 @@ must be running. A worker shares the container environment, while each test gets a fresh browser context and cleared BookIT bookings/rules. Run with one worker to keep the suite's resource usage predictable. -The four spec files contain focused, independent tests rather than one long test +The spec files contain focused, independent tests rather than one long test per file. Booking lifecycle and concurrency, calendar navigation and drag/edit consistency, language persistence, mobile layout, rule validation/lifecycle, and UI/API permissions have separate results. Each test gets its own login and data reset; the services stay shared for the worker. +Accessibility regressions cover keyboard date/time editing, leap-day selection, +focus restoration (including calendars inside dialogs), required-date validation, +and the mobile date picker layout. + Prefer `getByRole` with an accessible name and scope it to the relevant dialog or -navigation when needed. Use `getByLabel` for native date/time inputs, which have -no useful implicit role; do not add artificial roles just to satisfy a selector. +navigation when needed. Date/time fields use React Aria's named groups and +editable spinbutton segments. `date-time-helpers.ts` enters values through the +keyboard and asserts their accessible numeric values. These are the controls' +actual semantics, not artificial roles added to satisfy a selector. E2E source uses the same Vite+ formatter as the application: diff --git a/e2e/accessibility.spec.ts b/e2e/accessibility.spec.ts new file mode 100644 index 0000000..187c8e9 --- /dev/null +++ b/e2e/accessibility.spec.ts @@ -0,0 +1,80 @@ +import { test, expect } from "./fixtures"; +import { bookingDate, fillBooking, graphql } from "./booking-helpers"; +import { dateSegments, expectSegments, fillSegments } from "./date-time-helpers"; + +test("booking dates support keyboard editing and calendar selection without losing the time", async ({ + page, +}) => { + await page.goto("/new-event"); + + const begins = page.getByRole("group", { name: "Begins at", exact: true }); + await fillSegments(begins, { year: 2028, month: 2, day: 28, hour: 12, minute: 30 }); + + const minute = begins.getByRole("spinbutton", { name: "minute, Begins at", exact: true }); + await minute.press("ArrowUp"); + await expectSegments(begins, { minute: 31 }); + await minute.press("ArrowDown"); + await minute.press("Tab"); + + const trigger = page.getByRole("button", { name: "Choose date for Begins at", exact: true }); + await expect(trigger).toBeFocused(); + await trigger.press("Enter"); + + const calendar = page.getByRole("dialog", { name: "Begins at calendar", exact: true }); + const selectedDay = calendar.getByRole("button", { name: /28 February 2028 selected/ }); + await expect(selectedDay).toBeFocused(); + await selectedDay.press("ArrowRight"); + + const leapDay = calendar.getByRole("button", { name: "Tuesday, 29 February 2028", exact: true }); + await expect(leapDay).toBeFocused(); + await leapDay.press("Enter"); + + await expect(calendar).not.toBeVisible(); + await expect(trigger).toBeFocused(); + await expectSegments(begins, { year: 2028, month: 2, day: 29, hour: 12, minute: 30 }); + + await trigger.press("Enter"); + await page.keyboard.press("Escape"); + await expect(calendar).not.toBeVisible(); + await expect(trigger).toBeFocused(); + + await page.setViewportSize({ width: 390, height: 844 }); + await trigger.click(); + await expect(calendar).toBeVisible(); + const bounds = await calendar.boundingBox(); + if (!bounds) throw new Error("The date picker has no visible bounds"); + expect(bounds.x).toBeGreaterThanOrEqual(0); + expect(bounds.x + bounds.width).toBeLessThanOrEqual(390); + expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(390); +}); + +test("an empty required date is described as invalid and prevents booking submission", async ({ + page, +}) => { + await page.goto("/new-event"); + await fillBooking(page, "E2E incomplete date"); + + const begins = page.getByRole("group", { name: "Begins at", exact: true }); + for (const segment of await begins.getByRole("spinbutton").all()) { + await segment.press("Home"); + await segment.press("Delete"); + } + const day = begins.getByRole("spinbutton", { name: "day, Begins at", exact: true }); + await page.getByRole("button", { name: "Save booking", exact: true }).click(); + + await expect(page).toHaveURL(/\/new-event$/); + await expect(day).toHaveAttribute("aria-invalid", "true"); + await expect(day).toBeFocused(); + await expect(page.getByText("Please fill out this field.", { exact: true })).toBeVisible(); + await expect(day).toHaveAccessibleDescription(/Please fill out this field\./); + + const { events } = await graphql<{ events: { title: string }[] }>(page, "{ events { title } }"); + expect(events).toHaveLength(0); + + await fillSegments(begins, { ...dateSegments(await bookingDate(page)), hour: 12, minute: 0 }); + await page.getByRole("button", { name: "Save booking", exact: true }).click(); + await expect(page).toHaveURL(/\/$/); + + const saved = await graphql<{ events: { title: string }[] }>(page, "{ events { title } }"); + expect(saved.events).toEqual([{ title: "E2E incomplete date" }]); +}); diff --git a/e2e/admin-rules.spec.ts b/e2e/admin-rules.spec.ts index 24a8371..fc4a871 100644 --- a/e2e/admin-rules.spec.ts +++ b/e2e/admin-rules.spec.ts @@ -1,8 +1,40 @@ import { test, expect } from "./fixtures"; import { bookingDate } from "./booking-helpers"; +import { dateSegments, expectSegments, fillSegments } from "./date-time-helpers"; test.use({ role: "admin" }); +test("the rule calendar returns keyboard focus without closing its parent dialog", async ({ + page, +}) => { + await page.getByRole("navigation").getByRole("link", { name: "Rules", exact: true }).click(); + await page.getByRole("button", { name: "New rule", exact: true }).click(); + + const form = page.getByRole("dialog", { name: "New rule", exact: true }); + const trigger = form.getByRole("button", { name: "Choose date for End date", exact: true }); + await trigger.press("Enter"); + + const calendar = page.getByRole("dialog", { name: "End date calendar", exact: true }); + const day = calendar.getByRole("button", { name: /31 December 2040 selected/ }); + await expect(day).toBeFocused(); + await day.press("ArrowLeft"); + await page.keyboard.press("Enter"); + + await expect(calendar).not.toBeVisible(); + await expect(trigger).toBeFocused(); + await expectSegments(form.getByRole("group", { name: "End date", exact: true }), { + year: 2040, + month: 12, + day: 30, + }); + + await trigger.press("Enter"); + await page.keyboard.press("Escape"); + await expect(calendar).not.toBeVisible(); + await expect(form).toBeVisible(); + await expect(trigger).toBeFocused(); +}); + test("a new rule has the original defaults and requires a selected weekday", async ({ page }) => { await page.getByRole("navigation").getByRole("link", { name: "Rules", exact: true }).click(); await expect(page.getByRole("heading", { name: "Rules", exact: true })).toBeVisible(); @@ -14,16 +46,28 @@ test("a new rule has the original defaults and requires a selected weekday", asy await expect(form.getByRole("combobox", { name: "Availability", exact: true })).toHaveValue( "true", ); - await expect(form.getByLabel("End date", { exact: true })).toHaveValue("2040-12-31"); + await expectSegments(form.getByRole("group", { name: "End date", exact: true }), { + year: 2040, + month: 12, + day: 31, + }); await expect(form.getByRole("checkbox", { checked: true })).toHaveCount(0); await form.getByRole("textbox", { name: "Title", exact: true }).fill("E2E room maintenance"); await form.getByRole("spinbutton", { name: "Priority", exact: true }).fill("2"); await form.getByRole("combobox", { name: "Availability", exact: true }).selectOption("false"); - await form.getByLabel("Start date", { exact: true }).fill(await bookingDate(page)); - await form.getByLabel("End date", { exact: true }).fill("2040-12-31"); - await form.getByLabel("Start time", { exact: true }).fill("12:00"); - await form.getByLabel("End time", { exact: true }).fill("13:00"); + await fillSegments( + form.getByRole("group", { name: "Start date", exact: true }), + dateSegments(await bookingDate(page)), + ); + await fillSegments(form.getByRole("group", { name: "Start time", exact: true }), { + hour: 12, + minute: 0, + }); + await fillSegments(form.getByRole("group", { name: "End time", exact: true }), { + hour: 13, + minute: 0, + }); await form.getByRole("checkbox", { name: "Storhubben", exact: true }).check(); await form .getByRole("textbox", { name: "Description", exact: true }) @@ -43,10 +87,18 @@ test("an administrator creates, views and deletes a recurring booking rule", asy await form.getByRole("textbox", { name: "Title", exact: true }).fill("E2E room maintenance"); await form.getByRole("spinbutton", { name: "Priority", exact: true }).fill("2"); await form.getByRole("combobox", { name: "Availability", exact: true }).selectOption("false"); - await form.getByLabel("Start date", { exact: true }).fill(await bookingDate(page)); - await form.getByLabel("End date", { exact: true }).fill("2040-12-31"); - await form.getByLabel("Start time", { exact: true }).fill("12:00"); - await form.getByLabel("End time", { exact: true }).fill("13:00"); + await fillSegments( + form.getByRole("group", { name: "Start date", exact: true }), + dateSegments(await bookingDate(page)), + ); + await fillSegments(form.getByRole("group", { name: "Start time", exact: true }), { + hour: 12, + minute: 0, + }); + await fillSegments(form.getByRole("group", { name: "End time", exact: true }), { + hour: 13, + minute: 0, + }); await form.getByRole("checkbox", { name: "Storhubben", exact: true }).check(); await form .getByRole("textbox", { name: "Description", exact: true }) diff --git a/e2e/booking-helpers.ts b/e2e/booking-helpers.ts index ceb5a21..7fe86b5 100644 --- a/e2e/booking-helpers.ts +++ b/e2e/booking-helpers.ts @@ -1,4 +1,5 @@ import { expect, type Page } from "@playwright/test"; +import { dateSegments, fillSegments } from "./date-time-helpers"; export async function bookingDate(page: Page): Promise<string> { // Match the browser's date, including CI's configured timezone. @@ -27,8 +28,16 @@ export async function fillBooking(page: Page, title: string) { await page.getByRole("combobox", { name: "Booking as", exact: true }).selectOption("digit"); await page.getByRole("checkbox", { name: "Storhubben", exact: true }).check(); - await page.getByLabel("Begins at", { exact: true }).fill(`${date}T12:00`); - await page.getByLabel("Ends at", { exact: true }).fill(`${date}T13:00`); + await fillSegments(page.getByRole("group", { name: "Begins at", exact: true }), { + ...dateSegments(date), + hour: 12, + minute: 0, + }); + await fillSegments(page.getByRole("group", { name: "Ends at", exact: true }), { + ...dateSegments(date), + hour: 13, + minute: 0, + }); await page .getByRole("textbox", { name: "Description", exact: true }) diff --git a/e2e/calendar.spec.ts b/e2e/calendar.spec.ts index 281ac87..ed61a0d 100644 --- a/e2e/calendar.spec.ts +++ b/e2e/calendar.spec.ts @@ -1,5 +1,6 @@ import { test, expect } from "./fixtures"; import { createBooking, graphql } from "./booking-helpers"; +import { expectSegments } from "./date-time-helpers"; test("room filters and period navigation show the expected bookings", async ({ page }) => { const title = "E2E calendar meeting"; @@ -76,10 +77,19 @@ test("editing immediately after a calendar drag preserves the moved times", asyn await event.first().click(); await page.getByRole("button", { name: "Edit", exact: true }).click(); - const editStart = await page.getByLabel("Begins at", { exact: true }).inputValue(); - expect(await page.evaluate((value) => new Date(value).toISOString(), editStart)).toBe( - movedBooking.start, - ); + const movedLocalTime = await page.evaluate((value) => { + const date = new Date(value); + + return { + year: date.getFullYear(), + month: date.getMonth() + 1, + day: date.getDate(), + hour: date.getHours(), + minute: date.getMinutes(), + }; + }, movedBooking.start); + + await expectSegments(page.getByRole("group", { name: "Begins at", exact: true }), movedLocalTime); await page .getByRole("textbox", { name: "Description", exact: true }) diff --git a/e2e/date-time-helpers.ts b/e2e/date-time-helpers.ts new file mode 100644 index 0000000..385d548 --- /dev/null +++ b/e2e/date-time-helpers.ts @@ -0,0 +1,34 @@ +import { expect, type Locator } from "@playwright/test"; + +type Segments = Partial<Record<"year" | "month" | "day" | "hour" | "minute", number>>; + +export function dateSegments(value: string): Segments { + const [year, month, day] = value.split("-").map(Number); + if (year === undefined || month === undefined || day === undefined) { + throw new Error(`Expected an ISO date, received ${value}`); + } + + return { year, month, day }; +} + +export async function fillSegments(field: Locator, values: Segments) { + for (const [name, value] of Object.entries(values)) { + const segment = field.getByRole("spinbutton", { name: new RegExp(`^${name},`) }); + + // Home resets the segment and its typing buffer before entering a value. + await segment.press("Home"); + await segment.pressSequentially(String(value)); + await segment.press("Tab"); + + await expect(segment).toHaveAttribute("aria-valuenow", String(value)); + } +} + +export async function expectSegments(field: Locator, values: Segments) { + for (const [name, value] of Object.entries(values)) { + await expect(field.getByRole("spinbutton", { name: new RegExp(`^${name},`) })).toHaveAttribute( + "aria-valuenow", + String(value), + ); + } +} diff --git a/frontend/package.json b/frontend/package.json index 1c3ece1..61f9095 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,6 +11,7 @@ "dependencies": { "@fullcalendar/react": "7.1.0", "@graphql-typed-document-node/core": "^3.2.0", + "@internationalized/date": "3.12.4", "@tanstack/react-query": "^5.102.8", "@tanstack/react-router": "^1.170.32", "class-variance-authority": "^0.7.1", @@ -21,6 +22,7 @@ "lucide-react": "^1.41.0", "radix-ui": "^1.6.7", "react": "^19.2.8", + "react-aria-components": "1.21.1", "react-dom": "^19.2.8", "sonner": "^2.0.8", "tailwind-merge": "^3.6.0", diff --git a/frontend/src/components/booking-form.tsx b/frontend/src/components/booking-form.tsx index 314ffab..f3df9e6 100644 --- a/frontend/src/components/booking-form.tsx +++ b/frontend/src/components/booking-form.tsx @@ -20,6 +20,7 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Checkbox } from "@/components/ui/checkbox"; +import { DatePicker } from "@/components/date-time-fields"; import { Dialog, DialogContent, @@ -191,14 +192,8 @@ export function BookingForm({ </div> </fieldset> <div className="grid gap-5 sm:grid-cols-2"> - <div className="field"> - <Label htmlFor="start">{t("Begins at", "Börjar")}</Label> - <Input id="start" name="start" type="datetime-local" required defaultValue={start} /> - </div> - <div className="field"> - <Label htmlFor="end">{t("Ends at", "Slutar")}</Label> - <Input id="end" name="end" type="datetime-local" required defaultValue={end} /> - </div> + <DatePicker label={t("Begins at", "Börjar")} name="start" defaultValue={start} withTime /> + <DatePicker label={t("Ends at", "Slutar")} name="end" defaultValue={end} withTime /> </div> <div className="field"> <Label htmlFor="description">{t("Description", "Beskrivning")}</Label> diff --git a/frontend/src/components/date-time-fields.tsx b/frontend/src/components/date-time-fields.tsx new file mode 100644 index 0000000..abf0578 --- /dev/null +++ b/frontend/src/components/date-time-fields.tsx @@ -0,0 +1,197 @@ +import { useRef, useState } from "react"; +import { CalendarDays, ChevronLeft, ChevronRight } from "lucide-react"; +import { Popover } from "radix-ui"; +import { + type CalendarDate, + CalendarDateTime, + parseDate, + parseDateTime, + parseTime, + toCalendarDate, +} from "@internationalized/date"; +import { + Button as CalendarButton, + Calendar, + CalendarCell, + CalendarGrid, + CalendarGridBody, + CalendarGridHeader, + CalendarHeaderCell, + DateField, + DateInput, + DateSegment, + FieldError, + Heading, + I18nProvider, + Label, + TimeField, +} from "react-aria-components"; +import { Button, buttonVariants } from "@/components/ui/button"; +import { useLanguage } from "@/lib/language"; + +type FieldProps = { + label: string; + name: string; + defaultValue: string; +}; + +const inputClass = + "flex min-h-10 min-w-0 flex-1 items-center rounded-md border bg-background px-3 py-2 text-sm focus-within:border-ring focus-within:ring-2 focus-within:ring-ring/50 data-invalid:border-destructive"; + +function Segments() { + return ( + <DateInput className={inputClass}> + {(segment) => ( + <DateSegment + segment={segment} + className="rounded-sm px-0.5 tabular-nums outline-none focus:bg-primary focus:text-primary-foreground data-placeholder:text-muted-foreground data-[type=literal]:px-0" + /> + )} + </DateInput> + ); +} + +export function DatePicker({ + label, + name, + defaultValue, + withTime = false, +}: FieldProps & { withTime?: boolean }) { + const { language, t } = useLanguage(); + const [open, setOpen] = useState(false); + const calendarRef = useRef<HTMLDivElement>(null); + const [value, setValue] = useState<CalendarDate | CalendarDateTime | null>(() => + withTime ? parseDateTime(defaultValue) : parseDate(defaultValue), + ); + + return ( + <I18nProvider locale={language === "sv" ? "sv-SE" : "en-GB"}> + <DateField + name={name} + value={value} + onChange={setValue} + granularity={withTime ? "minute" : "day"} + hourCycle={24} + shouldForceLeadingZeros + isRequired + validationBehavior="native" + className="space-y-2" + > + <Label className="text-sm font-medium">{label}</Label> + + <div className="flex items-center gap-2"> + <Segments /> + + <Popover.Root open={open} onOpenChange={setOpen}> + <Popover.Trigger asChild> + <Button + type="button" + variant="outline" + size="icon" + aria-label={t(`Choose date for ${label}`, `Välj datum för ${label}`)} + > + <CalendarDays className="size-4" aria-hidden="true" /> + </Button> + </Popover.Trigger> + + <Popover.Portal> + <Popover.Content + align="start" + sideOffset={8} + collisionPadding={16} + onOpenAutoFocus={(event) => { + // Let Radix establish its focus scope before focusing the calendar. + // This also works when the picker is nested inside a rule dialog. + event.preventDefault(); + calendarRef.current + ?.querySelector<HTMLElement>('[role="button"][tabindex="0"]') + ?.focus(); + }} + aria-label={t(`${label} calendar`, `Kalender för ${label}`)} + className="z-50 w-fit max-w-[calc(100vw-2rem)] rounded-lg border bg-popover p-3 text-popover-foreground shadow-md" + > + <Calendar + ref={calendarRef} + aria-label={label} + value={value ? toCalendarDate(value) : null} + onChange={(date) => { + setValue((current) => + current + ? current.set({ year: date.year, month: date.month, day: date.day }) + : withTime + ? new CalendarDateTime(date.year, date.month, date.day) + : date, + ); + setOpen(false); + }} + firstDayOfWeek="mon" + > + <div className="mb-2 flex items-center justify-between gap-2"> + <CalendarButton + slot="previous" + className={buttonVariants({ variant: "ghost", size: "icon" })} + > + <ChevronLeft className="size-4" aria-hidden="true" /> + </CalendarButton> + + <Heading className="text-sm font-semibold" /> + + <CalendarButton + slot="next" + className={buttonVariants({ variant: "ghost", size: "icon" })} + > + <ChevronRight className="size-4" aria-hidden="true" /> + </CalendarButton> + </div> + + <CalendarGrid> + <CalendarGridHeader> + {(day) => ( + <CalendarHeaderCell className="pb-2 text-xs font-normal text-muted-foreground"> + {day} + </CalendarHeaderCell> + )} + </CalendarGridHeader> + <CalendarGridBody> + {(date) => ( + <CalendarCell + date={date} + className="flex size-9 cursor-pointer items-center justify-center rounded-md text-sm outline-none hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring data-selected:bg-primary data-selected:text-primary-foreground data-[outside-month]:text-muted-foreground data-disabled:opacity-40" + /> + )} + </CalendarGridBody> + </CalendarGrid> + </Calendar> + </Popover.Content> + </Popover.Portal> + </Popover.Root> + </div> + + <FieldError className="block text-sm text-destructive" /> + </DateField> + </I18nProvider> + ); +} + +export function TimePicker({ label, name, defaultValue }: FieldProps) { + const { language } = useLanguage(); + + return ( + <I18nProvider locale={language === "sv" ? "sv-SE" : "en-GB"}> + <TimeField + name={name} + defaultValue={parseTime(defaultValue)} + granularity="minute" + hourCycle={24} + shouldForceLeadingZeros + isRequired + validationBehavior="native" + className="space-y-2" + > + <Label className="text-sm font-medium">{label}</Label> + <Segments /> + <FieldError className="block text-sm text-destructive" /> + </TimeField> + </I18nProvider> + ); +} diff --git a/frontend/src/pages/rules.tsx b/frontend/src/pages/rules.tsx index c50f179..de87771 100644 --- a/frontend/src/pages/rules.tsx +++ b/frontend/src/pages/rules.tsx @@ -21,6 +21,7 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Checkbox } from "@/components/ui/checkbox"; +import { DatePicker, TimePicker } from "@/components/date-time-fields"; import { Badge } from "@/components/ui/badge"; import { Card } from "@/components/ui/card"; import { @@ -125,8 +126,9 @@ export function RulesPage() { room: selected, start_date: formText(form, "start_date"), end_date: formText(form, "end_date"), - start_time: formText(form, "start_time"), - end_time: formText(form, "end_time"), + // Accessible time fields serialize ISO times; the API uses minute precision. + start_time: formText(form, "start_time").slice(0, 5), + end_time: formText(form, "end_time").slice(0, 5), }; if (rule.end_date < rule.start_date || rule.end_time <= rule.start_time) throw new Error( @@ -357,40 +359,22 @@ export function RulesPage() { <option value="false">{t("Blocked", "Spärrad")}</option> </select> </div> - <div className="field"> - <Label htmlFor="start_date">{t("Start date", "Startdatum")}</Label> - <Input - id="start_date" - name="start_date" - type="date" - required - defaultValue={format(new Date(), "yyyy-MM-dd")} - /> - </div> - <div className="field"> - <Label htmlFor="end_date">{t("End date", "Slutdatum")}</Label> - <Input - id="end_date" - name="end_date" - type="date" - required - defaultValue="2040-12-31" - /> - </div> - <div className="field"> - <Label htmlFor="start_time">{t("Start time", "Starttid")}</Label> - <Input - id="start_time" - name="start_time" - type="time" - required - defaultValue="08:00" - /> - </div> - <div className="field"> - <Label htmlFor="end_time">{t("End time", "Sluttid")}</Label> - <Input id="end_time" name="end_time" type="time" required defaultValue="17:00" /> - </div> + <DatePicker + label={t("Start date", "Startdatum")} + name="start_date" + defaultValue={format(new Date(), "yyyy-MM-dd")} + /> + <DatePicker + label={t("End date", "Slutdatum")} + name="end_date" + defaultValue="2040-12-31" + /> + <TimePicker + label={t("Start time", "Starttid")} + name="start_time" + defaultValue="08:00" + /> + <TimePicker label={t("End time", "Sluttid")} name="end_time" defaultValue="17:00" /> </div> <fieldset> <legend className="text-sm font-medium mb-3">{t("Weekdays", "Veckodagar")}</legend> diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5dd8756..38c488b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -240,6 +240,9 @@ importers: '@graphql-typed-document-node/core': specifier: ^3.2.0 version: 3.2.0(graphql@16.14.2) + '@internationalized/date': + specifier: 3.12.4 + version: 3.12.4 '@tanstack/react-query': specifier: ^5.102.8 version: 5.102.8(react@19.2.8) @@ -270,6 +273,9 @@ importers: react: specifier: ^19.2.8 version: 19.2.8 + react-aria-components: + specifier: 1.21.1 + version: 1.21.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) react-dom: specifier: ^19.2.8 version: 19.2.8(react@19.2.8) @@ -752,6 +758,15 @@ packages: '@hapi/topo@5.1.0': resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} + '@internationalized/date@3.12.4': + resolution: {integrity: sha512-M1dEn4c1U1HsSlaVR8upZtSqvXrTkHDfv18H01uCSJyjVLDxnBR38v/fMxecmlwXKR4i9HeZcmgQAPE6A+aGJQ==} + + '@internationalized/number@3.6.8': + resolution: {integrity: sha512-8UmMFia46DUt+k97zKd9fKWXcWHR+k8ae3eYzILETuT2KbIvLyOfac7zesw+sJdRAAZ7Q9pM1Mk22aXp2LD0Ig==} + + '@internationalized/string@3.2.10': + resolution: {integrity: sha512-PDx6//vHSpRnHfxqMqto11zQvhsaU74O3mKv2F/0eicGZcl9NLjQmGlbHz/LsJh5tLKp4A4L7ZVTzN1/MmMTvA==} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -1939,6 +1954,11 @@ packages: '@radix-ui/rect@1.1.3': resolution: {integrity: sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==} + '@react-types/shared@3.36.1': + resolution: {integrity: sha512-AzsuD9OfxTOZMMvTRhlN3oHBwOmFN7tDh27LzqmHt4+uOgPhJT7ZM7/kVs/8/o0WxayMUIk3hBmCFRHv1FUoag==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + '@redis/bloom@6.2.1': resolution: {integrity: sha512-huQgNLaCIZfQ9SeLn4q9124uOUd8HbZDYHwwUzNcRgHqCHiHKl2dDxMqJCeWh8cMqZAoWuHR8XnWbDMIf+o7ag==} engines: {node: '>= 20.0.0'} @@ -1990,6 +2010,9 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@swc/helpers@0.5.23': + resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==} + '@tailwindcss/node@4.3.3': resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==} @@ -2945,6 +2968,9 @@ packages: classnames@2.5.1: resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -4319,6 +4345,18 @@ packages: rc9@3.1.0: resolution: {integrity: sha512-ufjkNVzbRHKcCOmTahZkmVsyc3W+MSk3jY03m+a7tGHkIsdVMG9l10/3HvFbWkkKzY5VFp3pkRsIo/UYgmFL7Q==} + react-aria-components@1.21.1: + resolution: {integrity: sha512-J88WflYY0z+EhLX0ce+GjFlQ3ag3ubIgfUTjpKSrBQBu92Xtl4Ub9o118HItUddtbk1Ido0jzyPy94/klZy1hg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + react-aria@3.52.1: + resolution: {integrity: sha512-fdZZruC9/x/joCg0mhKGs5aHpwrXLCSZ4GOJmhhYyiE0ffyEsk9MLFt9LCOCF9tn8ErTD+UDQP4oDUSlZkmpCg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom@19.2.8: resolution: {integrity: sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==} peerDependencies: @@ -4347,6 +4385,11 @@ packages: '@types/react': optional: true + react-stately@3.50.0: + resolution: {integrity: sha512-TnckvpDQGU0672wEaLZqdzvhuSeXWjgW6vijMWxiKOxc8CosQUfPGISdcZyfHqjX3XMjEtRxj4w9HumvX4h4mw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-style-singleton@2.2.3: resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} engines: {node: '>=10'} @@ -5385,6 +5428,18 @@ snapshots: dependencies: '@hapi/hoek': 9.3.0 + '@internationalized/date@3.12.4': + dependencies: + '@swc/helpers': 0.5.23 + + '@internationalized/number@3.6.8': + dependencies: + '@swc/helpers': 0.5.23 + + '@internationalized/string@3.2.10': + dependencies: + '@swc/helpers': 0.5.23 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.6.0 @@ -6505,6 +6560,10 @@ snapshots: '@radix-ui/rect@1.1.3': {} + '@react-types/shared@3.36.1(react@19.2.8)': + dependencies: + react: 19.2.8 + '@redis/bloom@6.2.1(@redis/client@6.2.1)': dependencies: '@redis/client': 6.2.1 @@ -6537,6 +6596,10 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@swc/helpers@0.5.23': + dependencies: + tslib: 2.8.1 + '@tailwindcss/node@4.3.3': dependencies: '@jridgewell/remapping': 2.3.5 @@ -7414,6 +7477,8 @@ snapshots: classnames@2.5.1: {} + client-only@0.0.1: {} + cliui@8.0.1: dependencies: string-width: 4.2.3 @@ -8879,6 +8944,32 @@ snapshots: defu: 6.1.7 destr: 2.0.5 + react-aria-components@1.21.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8): + dependencies: + '@internationalized/date': 3.12.4 + '@internationalized/string': 3.2.10 + '@react-types/shared': 3.36.1(react@19.2.8) + '@swc/helpers': 0.5.23 + client-only: 0.0.1 + react: 19.2.8 + react-aria: 3.52.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react-dom: 19.2.8(react@19.2.8) + react-stately: 3.50.0(react@19.2.8) + + react-aria@3.52.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8): + dependencies: + '@internationalized/date': 3.12.4 + '@internationalized/number': 3.6.8 + '@internationalized/string': 3.2.10 + '@react-types/shared': 3.36.1(react@19.2.8) + '@swc/helpers': 0.5.23 + aria-hidden: 1.2.6 + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + react-stately: 3.50.0(react@19.2.8) + use-sync-external-store: 1.6.0(react@19.2.8) + react-dom@19.2.8(react@19.2.8): dependencies: react: 19.2.8 @@ -8905,6 +8996,16 @@ snapshots: optionalDependencies: '@types/react': 19.2.18 + react-stately@3.50.0(react@19.2.8): + dependencies: + '@internationalized/date': 3.12.4 + '@internationalized/number': 3.6.8 + '@internationalized/string': 3.2.10 + '@react-types/shared': 3.36.1(react@19.2.8) + '@swc/helpers': 0.5.23 + react: 19.2.8 + use-sync-external-store: 1.6.0(react@19.2.8) + react-style-singleton@2.2.3(@types/react@19.2.18)(react@19.2.8): dependencies: get-nonce: 1.0.1 From f9911dea7d8db941cd8dbb1913d3df08572d518b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 10:34:04 +0200 Subject: [PATCH 06/41] Improve dense calendar layouts and stress-test concurrent bookings --- backend/src/services/event.service.ts | 7 +- backend/src/tests/booking-transaction.test.ts | 4 +- e2e/README.md | 23 ++++++ e2e/compose.ts | 6 +- e2e/screenshots.ts | 53 +++++++++++++ e2e/stress-data.ts | 66 ++++++++++++++++ e2e/stress-helpers.ts | 54 +++++++++++++ e2e/stress.spec.ts | 75 +++++++++++++++++++ e2e/stress.ts | 28 +++++++ .../src/components/event-calendar-toolbar.tsx | 9 ++- frontend/src/components/event-calendar.tsx | 2 +- .../components/ui/event-calendar-views.tsx | 38 +++++----- frontend/src/lib/booking-colors.ts | 22 ++++++ frontend/src/pages/calendar.tsx | 15 ++-- frontend/src/pages/rules.tsx | 20 +++-- frontend/src/styles.css | 16 +++- 16 files changed, 393 insertions(+), 45 deletions(-) create mode 100644 e2e/screenshots.ts create mode 100644 e2e/stress-data.ts create mode 100644 e2e/stress-helpers.ts create mode 100644 e2e/stress.spec.ts create mode 100644 e2e/stress.ts create mode 100644 frontend/src/lib/booking-colors.ts diff --git a/backend/src/services/event.service.ts b/backend/src/services/event.service.ts index faf8c33..bbe1b0d 100644 --- a/backend/src/services/event.service.ts +++ b/backend/src/services/event.service.ts @@ -2,6 +2,7 @@ import { Prisma, PrismaClient, event, room } from "@prisma/client"; import { Error, User } from "../models"; import { Event } from "../models/event"; import { checkRules } from "./rule.service"; +import { setTimeout as delay } from "node:timers/promises"; /* * Events must end after they start @@ -199,7 +200,8 @@ export const withBookingTransaction = async ( prisma: PrismaClient, operation: (transaction: Prisma.TransactionClient) => Promise<Error | null>, ): Promise<Error | null> => { - for (let attempt = 0; attempt < 3; attempt++) { + const maxAttempts = 5; + for (let attempt = 0; attempt < maxAttempts; attempt++) { try { return await prisma.$transaction(operation, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable, @@ -208,6 +210,9 @@ export const withBookingTransaction = async ( }); } catch (error) { if (!isSerializationFailure(error)) throw error; + // Jitter prevents a burst of valid bookings from retrying in lockstep. + // The failed transaction has rolled back before we wait or try again. + if (attempt + 1 < maxAttempts) await delay(20 * 2 ** attempt + Math.random() * 30); } } return { diff --git a/backend/src/tests/booking-transaction.test.ts b/backend/src/tests/booking-transaction.test.ts index cd7659e..74c2167 100644 --- a/backend/src/tests/booking-transaction.test.ts +++ b/backend/src/tests/booking-transaction.test.ts @@ -112,7 +112,7 @@ describe("Booking transaction recovery", () => { expect(await withBookingTransaction(prisma, async () => null)).toMatchObject({ en: "A booking changed concurrently. Please try again.", }); - expect(transaction).toHaveBeenCalledTimes(3); + expect(transaction).toHaveBeenCalledTimes(5); }); it("retries the PostgreSQL adapter's direct commit conflict and returns the rechecked overlap", async () => { @@ -130,7 +130,7 @@ describe("Booking transaction recovery", () => { expect(await withBookingTransaction(prisma, async () => null)).toMatchObject({ en: "A booking changed concurrently. Please try again.", }); - expect(transaction).toHaveBeenCalledTimes(3); + expect(transaction).toHaveBeenCalledTimes(5); }); it.each([ diff --git a/e2e/README.md b/e2e/README.md index a28fbba..c1a1b90 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -40,6 +40,29 @@ For the manual smoke test, run `pnpm e2e:dev` and open the printed BookIT URL. The command prints synthetic login credentials and keeps the environment alive. Ctrl+C stops application processes and removes the test containers and network. +To populate that local preview with 252 bookings and 24 rules, run: + +```sh +pnpm exec tsx e2e/stress.ts <local-app-url> <local-gamma-url> +``` + +The script uses synthetic admin credentials, accepts only localhost URLs, and +resumes without duplicating matching fixtures. It submits six writes concurrently +and reports their p95 response time. The CI stress spec uses the same dataset to +check persistence, multi-room diagonal stripes, calendar views, rule pagination, +and mobile overflow. This is a bounded local stress check, not a production +capacity benchmark. The normal concurrency test also verifies that conflicting +bookings cannot double-book a room. + +For screenshots matching the 3840 × 2234 reference images: + +```sh +pnpm exec tsx e2e/screenshots.ts <local-app-url> <local-gamma-url> <output-directory> +``` + +This captures week, month, list and rules from the populated preview at a +1920 × 1117 CSS viewport and 2× pixel density. + Tests import the extended `test` and `expect` from `./fixtures`. The default page logs in through Gamma as `member` before the test starts. A spec can use `test.use({ role: "admin" })` or `test.use({ role: "outsider" })`; `loginAs` lets a diff --git a/e2e/compose.ts b/e2e/compose.ts index e471a50..45098c0 100644 --- a/e2e/compose.ts +++ b/e2e/compose.ts @@ -151,7 +151,11 @@ async function provisionClient(browser: Browser, gammaUrl: string, appUrl: strin } } -export async function loginAs(page: Page, environment: Environment, role: UserRole): Promise<void> { +export async function loginAs( + page: Page, + environment: Pick<Environment, "appUrl" | "gammaUrl">, + role: UserRole, +): Promise<void> { await page.context().clearCookies(); await page.goto(`${environment.appUrl}/api/login`); await page.waitForURL((url) => url.origin === environment.gammaUrl && url.pathname === "/login"); diff --git a/e2e/screenshots.ts b/e2e/screenshots.ts new file mode 100644 index 0000000..f7cdc8b --- /dev/null +++ b/e2e/screenshots.ts @@ -0,0 +1,53 @@ +import { chromium, expect } from "@playwright/test"; +import { mkdir } from "node:fs/promises"; +import { resolve } from "node:path"; +import { loginAs } from "./compose"; + +async function main() { + const [appUrl, gammaUrl, outputDirectory] = process.argv.slice(2); + if (!appUrl || !gammaUrl || !outputDirectory) + throw new Error("Provide local app URL, Gamma URL and output directory"); + for (const url of [appUrl, gammaUrl]) { + if (!["localhost", "127.0.0.1"].includes(new URL(url).hostname)) + throw new Error("Only localhost previews are supported"); + } + await mkdir(outputDirectory, { recursive: true }); + const browser = await chromium.launch(); + try { + // Match the supplied 3840 × 2234 Retina references at a 1920 × 1117 CSS viewport. + const page = await browser.newPage({ + baseURL: appUrl, + viewport: { width: 1920, height: 1117 }, + deviceScaleFactor: 2, + }); + await loginAs(page, { appUrl, gammaUrl }, "admin"); + await expect( + page.getByRole("button").filter({ hasText: "[Stress] Pub preparations · 1" }).first(), + ).toBeVisible(); + await page.screenshot({ path: resolve(outputDirectory, "week.png"), animations: "disabled" }); + + await page.getByRole("tab", { name: "Month view", exact: true }).click(); + await expect(page.getByRole("button", { name: /\+\d+ more/ }).first()).toBeVisible(); + await page.screenshot({ path: resolve(outputDirectory, "month.png"), animations: "disabled" }); + + await page.getByRole("tab", { name: "List view", exact: true }).click(); + const stripes = page + .getByRole("button") + .filter({ hasText: "[Stress] Pub preparations · 1" }) + .first(); + await stripes.scrollIntoViewIfNeeded(); + await page.evaluate(() => window.scrollTo(0, 0)); + await page.screenshot({ path: resolve(outputDirectory, "list.png"), animations: "disabled" }); + + await page.getByRole("navigation").getByRole("link", { name: "Rules", exact: true }).click(); + await expect(page.getByRole("row")).toHaveCount(11); + await page.screenshot({ path: resolve(outputDirectory, "rules.png"), animations: "disabled" }); + console.log(`Saved four 3840 × 2234 screenshots to ${outputDirectory}`); + } finally { + await browser.close(); + } +} +void main().catch((error: unknown) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/e2e/stress-data.ts b/e2e/stress-data.ts new file mode 100644 index 0000000..e253ab0 --- /dev/null +++ b/e2e/stress-data.ts @@ -0,0 +1,66 @@ +import type { InputEvent, InputRule, Room } from "../frontend/src/generated/graphql"; + +export function stressData(now = new Date()) { + const monday = new Date(now); + monday.setDate(now.getDate() - ((now.getDay() + 6) % 7)); + monday.setHours(0, 0, 0, 0); + function dateAt(day: number) { + const date = new Date(monday); + date.setDate(date.getDate() + day); + return date; + } + function dateText(day: number) { + const date = dateAt(day); + return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`; + } + const rooms: Room[] = ["BIG_HUB", "GROUP_ROOM", "CTC", "THE_CLOUD"]; + const titles = ["Study session", "Project planning", "Committee meeting", "Workshop"]; + const events: InputEvent[] = []; + + function booking(day: number, hour: number, duration: number, title: string, room: Room[]) { + const start = dateAt(day); + start.setHours(hour, 0, 0, 0); + const end = new Date(start.getTime() + duration * 3_600_000); + events.push({ + title: `[Stress] ${title} · ${day + 1}`, + description: "Synthetic local stress-test booking. Safe to remove with the test environment.", + start: start.toISOString(), + end: end.toISOString(), + room, + phone: "0701234567", + booked_as: "digit", + booking_terms: true, + }); + } + + for (let day = 0; day < 14; day++) { + for (const [index, room] of rooms.entries()) { + for (const hour of [8, 10, 12, 14]) { + booking(day, hour, 1.5, `${titles[index]} ${hour}:00`, [room]); + } + } + booking( + day, + 17, + 7, + ["Pub preparations", "Karaoke brunch", "Milkshakepub"][day % 3]!, + rooms.slice(0, 3), + ); + booking(day, 18, 7, "Late-night games and society planning", ["THE_CLOUD"]); + } + + const rules: InputRule[] = Array.from({ length: 24 }, (_, index) => ({ + title: `[Stress] ${index % 2 ? "Reading morning" : "Room maintenance"} ${index + 1}`, + description: "Synthetic recurring rule for table pagination and layout checks.", + start_date: dateText(index), + end_date: dateText(index + 7), + start_time: "06:00", + end_time: "07:00", + priority: index + 1, + allow: false, + day_mask: 127, + room: rooms.slice(0, (index % 4) + 1), + })); + + return { events, rules }; +} diff --git a/e2e/stress-helpers.ts b/e2e/stress-helpers.ts new file mode 100644 index 0000000..50b2cd1 --- /dev/null +++ b/e2e/stress-helpers.ts @@ -0,0 +1,54 @@ +import type { Page } from "@playwright/test"; +import { graphql } from "./booking-helpers"; +import { stressData } from "./stress-data"; + +export async function populateStress(page: Page, concurrency = 6) { + if (!Number.isInteger(concurrency) || concurrency < 1 || concurrency > 6) { + throw new Error("Concurrency must be 1–6"); + } + const existing = await graphql<{ + events: { title: string; start: string }[]; + rules: { title: string; start_date: string }[]; + }>(page, "{ events { title start } rules { title start_date } }"); + const data = stressData(); + const existingEvents = new Set(existing.events.map((event) => `${event.title}|${event.start}`)); + const existingRules = new Set( + existing.rules.map((rule) => `${rule.title}|${rule.start_date.slice(0, 10)}`), + ); + const events = data.events.filter( + (event) => !existingEvents.has(`${event.title}|${event.start}`), + ); + const rules = data.rules.filter((rule) => !existingRules.has(`${rule.title}|${rule.start_date}`)); + const timings: number[] = []; + + // Resume partial local runs without deleting existing bookings or hiding errors. + for (let offset = 0; offset < events.length; offset += concurrency) { + await Promise.all( + events.slice(offset, offset + concurrency).map(async (event) => { + const start = performance.now(); + const result = await graphql<{ createEvent: { en: string } | null }>( + page, + "mutation($event: InputEvent!) { createEvent(event: $event) { en } }", + { event }, + ); + if (result.createEvent) throw new Error(`${event.title}: ${result.createEvent.en}`); + timings.push(performance.now() - start); + }), + ); + } + for (const rule of rules) { + const result = await graphql<{ createRule: { en: string } | null }>( + page, + "mutation($rule: InputRule!) { createRule(rule: $rule) { en } }", + { rule }, + ); + if (result.createRule) throw new Error(result.createRule.en); + } + timings.sort((a, b) => a - b); + return { + createdEvents: events.length, + createdRules: rules.length, + concurrency, + writeP95Ms: timings[Math.floor(timings.length * 0.95)] ?? 0, + }; +} diff --git a/e2e/stress.spec.ts b/e2e/stress.spec.ts new file mode 100644 index 0000000..8fe9ac7 --- /dev/null +++ b/e2e/stress.spec.ts @@ -0,0 +1,75 @@ +import { test, expect } from "./fixtures"; +import { graphql } from "./booking-helpers"; +import { populateStress } from "./stress-helpers"; + +test.use({ role: "admin" }); + +test("a busy fortnight preserves bookings, diagonal stripes and rule pagination", async ({ + page, +}, testInfo) => { + const result = await populateStress(page); + expect(result.createdEvents).toBe(252); + expect(result.createdRules).toBe(24); + await testInfo.attach("stress-metrics.json", { + body: JSON.stringify(result), + contentType: "application/json", + }); + + const persisted = await graphql<{ events: { id: string }[]; rules: { id: string }[] }>( + page, + "{ events { id } rules { id } }", + ); + expect(persisted.events).toHaveLength(252); + expect(new Set(persisted.events.map((event) => event.id)).size).toBe(252); + expect(persisted.rules).toHaveLength(24); + + await page.reload(); + const stripedBooking = page + .getByRole("button") + .filter({ hasText: "[Stress] Pub preparations · 1" }) + .first(); + await expect(stripedBooking).toBeVisible(); + await expect(stripedBooking).toHaveCSS("background-image", /repeating-linear-gradient\(45deg/); + await stripedBooking.click(); + const details = page.getByRole("dialog", { name: "Booking details", exact: true }); + await expect(details).toContainText("Storhubben"); + await expect(details).toContainText("Grupprummet"); + await expect(details).toContainText("CTC"); + await page.keyboard.press("Escape"); + + // Arrow keys must change the view, not only the visual tab selection. + await page.getByRole("tab", { name: "Week view", exact: true }).focus(); + await page.keyboard.press("ArrowLeft"); + await expect(page.getByRole("tab", { name: "List view", exact: true })).toHaveAttribute( + "aria-selected", + "true", + ); + await expect(stripedBooking).toHaveCSS("background-image", /repeating-linear-gradient\(45deg/); + + await page.getByRole("tab", { name: "Month view", exact: true }).click(); + await page + .getByRole("button", { name: /\+\d+ more/ }) + .first() + .click(); + await expect(stripedBooking).toBeVisible(); + await expect(stripedBooking).toHaveCSS("background-image", /repeating-linear-gradient\(45deg/); + await stripedBooking.click(); + await expect(details).toBeVisible(); + await page.keyboard.press("Escape"); + + await page.getByRole("navigation").getByRole("link", { name: "Rules", exact: true }).click(); + await expect(page.getByRole("row")).toHaveCount(11); + await expect(page.getByText("Page 1 / 3", { exact: true })).toBeVisible(); + await page.getByRole("button", { name: "Next", exact: true }).click(); + await expect(page.getByText("Page 2 / 3", { exact: true })).toBeVisible(); + await page.getByRole("button", { name: "Next", exact: true }).click(); + await expect(page.getByRole("row")).toHaveCount(5); + await expect(page.getByRole("button", { name: "Next", exact: true })).toBeDisabled(); + + await page.setViewportSize({ width: 390, height: 844 }); + expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(390); + await page.getByRole("navigation").getByRole("link", { name: "Calendar", exact: true }).click(); + await page.getByRole("tab", { name: "List view", exact: true }).click(); + await expect(stripedBooking).toBeVisible(); + expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(390); +}); diff --git a/e2e/stress.ts b/e2e/stress.ts new file mode 100644 index 0000000..1752ed9 --- /dev/null +++ b/e2e/stress.ts @@ -0,0 +1,28 @@ +import { chromium } from "@playwright/test"; +import { loginAs } from "./compose"; +import { populateStress } from "./stress-helpers"; + +async function main() { + const [appUrl, gammaUrl] = process.argv.slice(2); + if (!appUrl || !gammaUrl) + throw new Error("Usage: pnpm exec tsx e2e/stress.ts <local app URL> <local Gamma URL>"); + for (const url of [appUrl, gammaUrl]) { + if (!["localhost", "127.0.0.1"].includes(new URL(url).hostname)) { + throw new Error("Stress fixtures may only be added to a localhost test environment"); + } + } + + const browser = await chromium.launch(); + try { + const page = await browser.newPage({ baseURL: appUrl }); + await loginAs(page, { appUrl, gammaUrl }, "admin"); + console.log(JSON.stringify(await populateStress(page, Number(process.argv[4] ?? 6)), null, 2)); + } finally { + await browser.close(); + } +} + +void main().catch((error: unknown) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/frontend/src/components/event-calendar-toolbar.tsx b/frontend/src/components/event-calendar-toolbar.tsx index d44b77b..4daa17d 100644 --- a/frontend/src/components/event-calendar-toolbar.tsx +++ b/frontend/src/components/event-calendar-toolbar.tsx @@ -63,13 +63,16 @@ export function EventCalendarToolbar({ {controller.view?.title} </h3> </div> - <Tabs value={controller.view?.type ?? availableViews[0]}> - <TabsList> + <Tabs + value={controller.view?.type ?? availableViews[0]} + onValueChange={(view) => controller.changeView(view)} + > + <TabsList className="gap-1 p-1 group-data-[orientation=horizontal]/tabs:h-11"> {availableViews.map((availableView) => ( <TabsTrigger key={availableView} value={availableView} - onClick={() => controller.changeView(availableView)} + className="min-w-16 px-4 py-2" aria-label={buttons[availableView]?.hint} > {buttons[availableView]?.text} diff --git a/frontend/src/components/event-calendar.tsx b/frontend/src/components/event-calendar.tsx index f1322fc..455cbbe 100644 --- a/frontend/src/components/event-calendar.tsx +++ b/frontend/src/components/event-calendar.tsx @@ -67,7 +67,7 @@ export function EventCalendar({ dir={direction === "rtl" ? "rtl" : undefined} > <EventCalendarToolbar - className="p-4" + className="px-4 py-5 sm:px-5 gap-y-4" controller={controller} availableViews={availableViews} addButton={addButton} diff --git a/frontend/src/components/ui/event-calendar-views.tsx b/frontend/src/components/ui/event-calendar-views.tsx index 4b7c5e7..5d2e7c0 100644 --- a/frontend/src/components/ui/event-calendar-views.tsx +++ b/frontend/src/components/ui/event-calendar-views.tsx @@ -20,9 +20,11 @@ const dayRowCommonClasses: CalendarOptions = { /* Day Row > List-Item Event ----------------------------------------------------------------------------------------------- */ - listItemEventClass: (info) => cn("mb-px p-px rounded-sm", info.isNarrow ? "mx-px" : "mx-0.5"), - listItemEventBeforeClass: (info) => cn("border-4", info.isNarrow ? "ms-0.5" : "ms-1"), - listItemEventInnerClass: (info) => (info.isNarrow ? `py-px ${xxsTextClass}` : "py-0.5 text-xs"), + listItemEventClass: (info) => cn("mb-1 p-0.5 rounded-sm", info.isNarrow ? "mx-px" : "mx-1"), + listItemEventBeforeClass: (info) => + cn("border-4 rounded-full border-(--fc-event-color)", info.isNarrow ? "ms-0.5" : "ms-1"), + listItemEventInnerClass: (info) => + cn("booking-event-label", info.isNarrow ? `py-0.5 ${xxsTextClass}` : "py-1 text-xs leading-4"), listItemEventTimeClass: (info) => cn(info.isNarrow ? "ps-0.5" : "ps-1", "whitespace-nowrap overflow-hidden shrink-1"), listItemEventTitleClass: (info) => @@ -99,9 +101,9 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa !info.isSelected && info.isDragging && "opacity-75", ) } - blockEventInnerClass="text-(--fc-event-contrast-color) print:text-black" + blockEventInnerClass="booking-event-label text-(--fc-event-contrast-color) print:text-black" blockEventTimeClass="whitespace-nowrap overflow-hidden" - blockEventTitleClass="whitespace-nowrap overflow-hidden" + blockEventTitleClass="whitespace-normal overflow-hidden line-clamp-2 leading-snug" /* Row Event ----------------------------------------------------------------------------------------- */ @@ -183,15 +185,15 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa "flex", info.isShort ? "flex-row items-center p-1 gap-1" - : ["flex-col", info.isNarrow ? "px-1 py-0.5" : "px-2 py-1"], + : ["flex-col", info.isNarrow ? "px-1 py-1" : "px-2 py-1.5"], info.isShort || info.isNarrow ? xxsTextClass : "text-xs", ) } columnEventTimeClass={(info) => - cn("order-1 shrink-100", !info.isShort && (info.isNarrow ? "pb-0.5" : "pb-1")) + cn("order-0 shrink-100", !info.isShort && (info.isNarrow ? "pb-0.5" : "pb-1")) } columnEventTitleClass={(info) => - cn("shrink-1", !info.isShort && (info.isNarrow ? "py-0.5" : "py-1")) + cn("order-1 shrink-1", !info.isShort && (info.isNarrow ? "py-0.5" : "py-1")) } /* More-Link @@ -213,7 +215,7 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa info.isDisabled && !info.inPopover && "bg-foreground/3", ) } - dayHeaderInnerClass="group mt-2 mx-2 flex flex-col items-center outline-none" + dayHeaderInnerClass="group mt-2 mb-3 mx-2 flex flex-col items-center gap-1 outline-none" dayHeaderContent={(info) => ( <> {info.weekdayText && ( @@ -292,7 +294,7 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa listDayFormat={{ day: "numeric" }} listDayAltFormat={{ month: "short", weekday: "short", forceCommas: true }} listDayClass={(info) => cn(!info.isLast && "border-b", "flex flex-row items-start")} - listDayHeaderClass="p-2 shrink-0 w-1/3 max-w-44 min-h-9 flex flex-row items-center gap-2" + listDayHeaderClass="px-3 py-4 sm:px-5 shrink-0 w-1/3 max-w-44 min-h-12 flex flex-row flex-wrap items-center gap-2" listDayHeaderInnerClass={(info) => cn( !info.level @@ -310,7 +312,7 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa : ["text-xs uppercase", info.hasNavLink && "hover:underline"], ) } - listDayBodyClass="grow min-w-0 py-2 gap-1" + listDayBodyClass="grow min-w-0 py-3 pe-3 sm:pe-5 gap-2" /* Single Month (in Multi-Month) ----------------------------------------------------------------------------------------- */ @@ -405,7 +407,7 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa cn("w-2 self-end justify-end", "border", info.isMinor && "border-dotted"), slotHeaderInnerClass: (info) => cn( - "relative ms-2 me-3 my-2", + "relative ms-2 me-3 my-0.5", info.isNarrow ? `-top-4 ${xxsTextClass}` : "-top-5 text-sm", info.isFirst && "hidden", ), @@ -421,14 +423,14 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa /* List-View > List-Item Event ------------------------------------------------------------------------------------- */ - listItemEventClass: "group p-2 rounded-s-full gap-2", - listItemEventBeforeClass: "mx-2 border-5", - listItemEventInnerClass: "gap-2 text-sm", - listItemEventTimeClass: - "shrink-0 w-1/2 max-w-40 whitespace-nowrap overflow-hidden text-ellipsis", + listItemEventClass: "group px-3 py-3 rounded-md gap-2", + listItemEventBeforeClass: "mx-2 border-5 rounded-full border-(--fc-event-color)", + listItemEventInnerClass: + "booking-event-label flex-wrap gap-x-4 gap-y-1 text-sm leading-6", + listItemEventTimeClass: "shrink-0 w-28 whitespace-nowrap", listItemEventTitleClass: (info) => cn( - "grow min-w-0 whitespace-nowrap overflow-hidden", + "grow min-w-0 basis-40 whitespace-normal break-words", info.event.url && "group-hover:underline", ), diff --git a/frontend/src/lib/booking-colors.ts b/frontend/src/lib/booking-colors.ts new file mode 100644 index 0000000..c0af27a --- /dev/null +++ b/frontend/src/lib/booking-colors.ts @@ -0,0 +1,22 @@ +import type { Room } from "@/generated/graphql"; +import { rooms } from "./rooms"; + +export function bookingRoomClass(selected: readonly (Room | null)[]) { + const mask = rooms.reduce( + (value, room, index) => value | (selected.includes(room.id) ? 1 << index : 0), + 0, + ); + const multiple = mask !== 0 && (mask & (mask - 1)) !== 0; + return `booking-rooms-${mask}${multiple ? " booking-multi-room" : ""}`; +} + +// There are only 15 nonempty combinations of the four rooms. Declarative classes +// keep colors correct after edits, filtering and view changes, not just on mount. +export const bookingRoomStyles = Array.from({ length: (1 << rooms.length) - 1 }, (_, index) => { + const mask = index + 1; + const colors = rooms.filter((_, bit) => mask & (1 << bit)).map((room) => room.color); + const stops = colors + .map((color, stop) => `${color} ${stop * 25}px ${(stop + 1) * 25}px`) + .join(", "); + return `.booking-rooms-${mask} { --booking-stripes: repeating-linear-gradient(45deg, ${stops}); }`; +}).join("\n"); diff --git a/frontend/src/pages/calendar.tsx b/frontend/src/pages/calendar.tsx index 5ce68b0..d8806a4 100644 --- a/frontend/src/pages/calendar.tsx +++ b/frontend/src/pages/calendar.tsx @@ -15,6 +15,7 @@ import { type Room, } from "@/generated/graphql"; import { rooms } from "@/lib/rooms"; +import { bookingRoomClass, bookingRoomStyles } from "@/lib/booking-colors"; import { parseDate, localInput } from "@/lib/dates"; import { useLanguage } from "@/lib/language"; import { useUser } from "@/lib/user"; @@ -87,6 +88,7 @@ export function CalendarPage() { }); return ( <> + <style>{bookingRoomStyles}</style> <h1 className="sr-only">{t("Calendar", "Kalender")}</h1> <div className="flex flex-wrap justify-between items-center gap-3 mb-4"> <div className="flex flex-wrap gap-2" aria-label={t("Room filters", "Rumsfilter")}> @@ -132,10 +134,12 @@ export function CalendarPage() { scrollTimeReset={false} selectable eventOverlap + slotEventOverlap={false} + eventMaxStack={2} eventTimeFormat={{ hour: "2-digit", minute: "2-digit", hour12: false }} slotHeaderFormat={{ hour: "2-digit", minute: "2-digit", hour12: false }} dayHeaderFormat={{ weekday: "short", day: "2-digit", month: "2-digit" }} - views={{ dayGridMonth: { dayHeaderFormat: { weekday: "short" } } }} + views={{ dayGridMonth: { dayHeaderFormat: { weekday: "short" }, dayMaxEvents: 3 } }} datesSet={(info) => setRange((current) => current.from === info.start.toISOString() && current.to === info.end.toISOString() @@ -169,14 +173,6 @@ export function CalendarPage() { ); }} - eventDidMount={(info) => { - const booking = bookings.find((event) => event.id === info.event.id); - const colors = rooms - .filter((room) => booking?.room.includes(room.id)) - .map((room) => room.color); - if (colors.length > 1) - info.el.style.backgroundImage = `repeating-linear-gradient(45deg, ${colors.map((color, i) => `${color} ${i * 25}px ${(i + 1) * 25}px`).join(", ")})`; - }} events={[ ...visible.map((event) => ({ id: event.id ?? undefined, @@ -185,6 +181,7 @@ export function CalendarPage() { end: parseDate(event.end), color: rooms.find((room) => event.room.includes(room.id))?.color, contrastColor: "#fff", + className: bookingRoomClass(event.room), editable: Boolean(user?.is_admin || user?.groups?.includes(event.booked_as)), durationEditable: false, })), diff --git a/frontend/src/pages/rules.tsx b/frontend/src/pages/rules.tsx index de87771..0e5ef05 100644 --- a/frontend/src/pages/rules.tsx +++ b/frontend/src/pages/rules.tsx @@ -186,7 +186,7 @@ export function RulesPage() { <Failure error={query.error} retry={() => void query.refetch()} /> ) : ( <Card className="p-0 overflow-hidden"> - <Table> + <Table className="min-w-[1080px] [&_th]:px-5 [&_th]:py-3 [&_td]:px-5 [&_td]:py-4 [&_td]:leading-6"> <TableHeader> <TableRow> {columns.map((column) => ( @@ -209,7 +209,7 @@ export function RulesPage() { </button> </TableHead> ))} - <TableHead> + <TableHead className="sticky right-0 bg-card"> <span className="sr-only">{t("Actions", "Åtgärder")}</span> </TableHead> </TableRow> @@ -217,27 +217,31 @@ export function RulesPage() { <TableBody> {pageRules.map((rule) => ( <TableRow key={rule.id}> - <TableCell className="max-w-72 font-medium">{rule.title}</TableCell> + <TableCell className="min-w-44 max-w-72 whitespace-normal font-medium"> + {rule.title} + </TableCell> <TableCell>{rule.priority}</TableCell> <TableCell className="whitespace-nowrap">{dateText(rule.start_date)}</TableCell> <TableCell className="whitespace-nowrap">{dateText(rule.end_date)}</TableCell> <TableCell className="whitespace-nowrap"> {rule.start_time}–{rule.end_time} </TableCell> - <TableCell> + <TableCell className="min-w-36 whitespace-normal"> {rule.room ?.filter((room): room is Room => Boolean(room)) .map(roomName) .join(", ")} </TableCell> - <TableCell>{dayText(rule.day_mask)}</TableCell> + <TableCell className="min-w-44 whitespace-normal"> + {dayText(rule.day_mask)} + </TableCell> <TableCell> <Badge variant={rule.allow ? "secondary" : "destructive"}> {rule.allow ? t("Allowed", "Tillåten") : t("Blocked", "Spärrad")} </Badge> </TableCell> - <TableCell> - <div className="flex items-center gap-1"> + <TableCell className="sticky right-0 bg-card"> + <div className="flex items-center gap-2"> <Button variant="ghost" size="sm" @@ -269,7 +273,7 @@ export function RulesPage() { {t("No booking rules yet.", "Inga bokningsregler ännu.")} </p> )} - <div className="flex items-center justify-end gap-3 border-t p-3 text-sm"> + <div className="flex items-center justify-end gap-3 border-t px-5 py-4 text-sm"> <span> {t("Page", "Sida")} {currentPage + 1} / {pageCount} </span> diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 5e7738f..015f496 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -39,8 +39,10 @@ color: var(--foreground); background: var(--background); } -* { - border-color: var(--color-border); +@layer base { + * { + border-color: var(--color-border); + } } body { margin: 0; @@ -95,6 +97,16 @@ textarea { font-size: 0.875rem; background: white; } +.booking-multi-room { + background-image: var(--booking-stripes); + color: white; +} +.booking-multi-room .booking-event-label > * { + background: rgb(0 0 0 / 55%); + border-radius: 2px; + color: white; + align-self: flex-start; +} @media (max-width: 760px) { .app-shell { padding: 1.5rem 1rem; From 2ba532fe0e9355266c5dca2d32e4f1527cbcd902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 10:58:54 +0200 Subject: [PATCH 07/41] Fix overlapping calendar labels and expose full rule details --- e2e/calendar-rules.spec.ts | 70 +++++++++++++++++ e2e/stress.spec.ts | 17 +++++ .../src/components/calendar-block-label.tsx | 38 ++++++++++ .../components/ui/event-calendar-views.tsx | 6 +- frontend/src/lib/calendar-blocks.test.ts | 76 +++++++++++++++++++ frontend/src/lib/calendar-blocks.ts | 54 +++++++++++++ frontend/src/pages/calendar.tsx | 31 +++++--- 7 files changed, 280 insertions(+), 12 deletions(-) create mode 100644 e2e/calendar-rules.spec.ts create mode 100644 frontend/src/components/calendar-block-label.tsx create mode 100644 frontend/src/lib/calendar-blocks.test.ts create mode 100644 frontend/src/lib/calendar-blocks.ts diff --git a/e2e/calendar-rules.spec.ts b/e2e/calendar-rules.spec.ts new file mode 100644 index 0000000..8eb9224 --- /dev/null +++ b/e2e/calendar-rules.spec.ts @@ -0,0 +1,70 @@ +import { test, expect } from "./fixtures"; +import { bookingDate, graphql } from "./booking-helpers"; + +test.use({ role: "admin" }); + +test("overlapping room restrictions share a compact label and respect room filters", async ({ + page, +}) => { + const date = await bookingDate(page); + + for (const [title, room] of [ + ["Maintenance", ["BIG_HUB", "GROUP_ROOM"]], + ["Reading", ["CTC"]], + ]) { + const result = await graphql<{ createRule: { en: string } | null }>( + page, + "mutation($rule: InputRule!) { createRule(rule: $rule) { en } }", + { + rule: { + title, + description: "The rooms are unavailable during this time.", + room, + start_date: date, + end_date: date, + start_time: "12:00", + end_time: "13:00", + day_mask: 127, + priority: 10, + allow: false, + }, + }, + ); + + expect(result.createRule).toBeNull(); + } + + await page.reload(); + const label = page.getByRole("button", { name: "Maintenance · Reading", exact: true }); + + await expect(label).toHaveCount(1); + await expect(label).toHaveCSS("text-overflow", "ellipsis"); + await expect(label).toHaveCSS("white-space", "nowrap"); + + await label.hover(); + const tooltip = page.getByRole("tooltip"); + await expect(tooltip).toContainText("Maintenance — The rooms are unavailable during this time."); + await expect(tooltip).toContainText("Reading — The rooms are unavailable during this time."); + await page.keyboard.press("Escape"); + await expect(tooltip).not.toBeVisible(); + + await label.focus(); + await expect(tooltip).toBeVisible(); + await page.keyboard.press("Escape"); + await expect(tooltip).not.toBeVisible(); + + await label.click(); + await expect(tooltip).toBeVisible(); + await expect(page).toHaveURL(/\/$/); + await page.keyboard.press("Escape"); + + await page.getByRole("button", { name: "CTC", exact: true }).click(); + await expect(label).toHaveCount(0); + await expect(page.getByRole("button", { name: "Maintenance", exact: true })).toHaveCount(1); + + await page.getByRole("button", { name: "Storhubben", exact: true }).click(); + await expect(page.getByRole("button", { name: "Maintenance", exact: true })).toHaveCount(1); + + await page.getByRole("button", { name: "Grupprummet", exact: true }).click(); + await expect(page.getByRole("button", { name: "Maintenance", exact: true })).toHaveCount(0); +}); diff --git a/e2e/stress.spec.ts b/e2e/stress.spec.ts index 8fe9ac7..34e2ee8 100644 --- a/e2e/stress.spec.ts +++ b/e2e/stress.spec.ts @@ -30,6 +30,23 @@ test("a busy fortnight preserves bookings, diagonal stripes and rule pagination" .first(); await expect(stripedBooking).toBeVisible(); await expect(stripedBooking).toHaveCSS("background-image", /repeating-linear-gradient\(45deg/); + + // Both the time and title must fit inside narrow striped cards. + await page.setViewportSize({ width: 1174, height: 1039 }); + const labelsFit = await stripedBooking.evaluate((card) => { + const bounds = card.getBoundingClientRect(); + const labels = [...card.querySelectorAll(".booking-event-label > *")]; + + return ( + labels.length === 2 && + labels.every((label) => { + const rect = label.getBoundingClientRect(); + return rect.left >= bounds.left && rect.right <= bounds.right; + }) + ); + }); + expect(labelsFit).toBe(true); + await stripedBooking.click(); const details = page.getByRole("dialog", { name: "Booking details", exact: true }); await expect(details).toContainText("Storhubben"); diff --git a/frontend/src/components/calendar-block-label.tsx b/frontend/src/components/calendar-block-label.tsx new file mode 100644 index 0000000..4357e37 --- /dev/null +++ b/frontend/src/components/calendar-block-label.tsx @@ -0,0 +1,38 @@ +import { useState } from "react"; +import { Tooltip } from "radix-ui"; + +export function CalendarBlockLabel({ title, description }: { title: string; description: string }) { + const [open, setOpen] = useState(false); + + return ( + <Tooltip.Provider delayDuration={200}> + <Tooltip.Root open={open} onOpenChange={setOpen}> + <Tooltip.Trigger asChild> + <button + type="button" + className="pointer-events-auto relative z-2 block w-full truncate px-2 py-1 text-left text-xs italic text-foreground/70 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring" + onMouseDownCapture={(event) => event.stopPropagation()} + onTouchStartCapture={(event) => event.stopPropagation()} + onClick={(event) => { + // Keep click/tap useful too; the default tooltip click handler closes it. + event.preventDefault(); + setOpen(true); + }} + > + {title} + </button> + </Tooltip.Trigger> + <Tooltip.Portal> + <Tooltip.Content + side="top" + sideOffset={6} + collisionPadding={12} + className="z-50 max-w-[min(24rem,calc(100vw-2rem))] whitespace-pre-line wrap-anywhere rounded-md border bg-popover px-4 py-3 text-sm leading-relaxed text-popover-foreground shadow-md" + > + {description} + </Tooltip.Content> + </Tooltip.Portal> + </Tooltip.Root> + </Tooltip.Provider> + ); +} diff --git a/frontend/src/components/ui/event-calendar-views.tsx b/frontend/src/components/ui/event-calendar-views.tsx index 5d2e7c0..4eae58a 100644 --- a/frontend/src/components/ui/event-calendar-views.tsx +++ b/frontend/src/components/ui/event-calendar-views.tsx @@ -101,9 +101,9 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa !info.isSelected && info.isDragging && "opacity-75", ) } - blockEventInnerClass="booking-event-label text-(--fc-event-contrast-color) print:text-black" - blockEventTimeClass="whitespace-nowrap overflow-hidden" - blockEventTitleClass="whitespace-normal overflow-hidden line-clamp-2 leading-snug" + blockEventInnerClass="booking-event-label min-w-0 max-w-full overflow-hidden text-(--fc-event-contrast-color) print:text-black" + blockEventTimeClass="min-w-0 max-w-full truncate" + blockEventTitleClass="min-w-0 max-w-full whitespace-normal wrap-anywhere overflow-hidden line-clamp-2 leading-snug" /* Row Event ----------------------------------------------------------------------------------------- */ diff --git a/frontend/src/lib/calendar-blocks.test.ts b/frontend/src/lib/calendar-blocks.test.ts new file mode 100644 index 0000000..5390446 --- /dev/null +++ b/frontend/src/lib/calendar-blocks.test.ts @@ -0,0 +1,76 @@ +import { describe, expect, it } from "vite-plus/test"; +import { calendarBlocks } from "./calendar-blocks"; + +function slot(title: string, start = "06", end = "07", description = "") { + return { + title, + description, + start: `2026-09-06T${start}:00:00Z`, + end: `2026-09-06T${end}:00:00Z`, + }; +} + +function summary(slots: ReturnType<typeof slot>[]) { + return calendarBlocks(slots).map(({ start, end, title }) => [ + start.getUTCHours(), + end.getUTCHours(), + title, + ]); +} + +describe("calendar blocked-period labels", () => { + it("deduplicates the same rule returned for multiple rooms", () => { + const rule = slot("Reading morning", "06", "07", "No bookings"); + const blocks = calendarBlocks([rule, rule, rule]); + + expect(blocks).toHaveLength(1); + expect(blocks[0]).toMatchObject({ + title: "Reading morning", + description: "Reading morning — No bookings", + }); + }); + + it("combines simultaneous titles while retaining distinct descriptions", () => { + const blocks = calendarBlocks([ + slot("Reading", "06", "07", "Quiet time"), + slot("Maintenance", "06", "07", "Room closed"), + slot("Reading", "06", "07", "Exam preparation"), + ]); + + expect(blocks).toHaveLength(1); + expect(blocks[0]?.title).toBe("Maintenance · Reading"); + expect(blocks[0]?.description).toBe( + "Maintenance — Room closed\nReading — Exam preparation\nReading — Quiet time", + ); + }); + + it("splits partial overlaps without extending any restriction", () => { + expect(summary([slot("A", "06", "08"), slot("B", "07", "09")])).toEqual([ + [6, 7, "A"], + [7, 8, "A · B"], + [8, 9, "B"], + ]); + }); + + it("joins adjacent identical labels but preserves gaps and title changes", () => { + expect( + summary([ + slot("A", "06", "07"), + slot("A", "07", "08"), + slot("B", "08", "09"), + slot("B", "10", "11"), + ]), + ).toEqual([ + [6, 8, "A"], + [8, 9, "B"], + [10, 11, "B"], + ]); + }); + + it("handles an empty room selection and does not mutate the source rules", () => { + expect(calendarBlocks([])).toEqual([]); + const rule = Object.freeze(slot("A")); + expect(calendarBlocks(Object.freeze([rule, rule]))).toHaveLength(1); + expect(rule).toEqual(slot("A")); + }); +}); diff --git a/frontend/src/lib/calendar-blocks.ts b/frontend/src/lib/calendar-blocks.ts new file mode 100644 index 0000000..b32def2 --- /dev/null +++ b/frontend/src/lib/calendar-blocks.ts @@ -0,0 +1,54 @@ +import { parseDate } from "./dates"; + +interface BlockedSlot { + start: string; + end: string; + title: string; + description?: string | null; +} + +interface CalendarBlock { + start: Date; + end: Date; + title: string; + description: string; +} + +// The API resolves rules separately for each room. Combine only their display, +// after room filtering, so simultaneous restrictions cannot paint overlapping labels. +export function calendarBlocks(slots: readonly BlockedSlot[]): CalendarBlock[] { + const intervals = slots.map((slot) => ({ + ...slot, + start: parseDate(slot.start).getTime(), + end: parseDate(slot.end).getTime(), + })); + const boundaries = [...new Set(intervals.flatMap(({ start, end }) => [start, end]))].sort( + (a, b) => a - b, + ); + const blocks: CalendarBlock[] = []; + + for (let index = 1; index < boundaries.length; index++) { + const start = boundaries[index - 1]!; + const end = boundaries[index]!; + const active = intervals.filter((slot) => slot.start <= start && slot.end >= end); + if (!active.length) continue; + + const title = [...new Set(active.map((slot) => slot.title))].sort().join(" · "); + const description = [ + ...new Set( + active.map((slot) => slot.title + (slot.description ? ` — ${slot.description}` : "")), + ), + ] + .sort() + .join("\n"); + const previous = blocks.at(-1); + + if (previous?.end.getTime() === start && previous.description === description) { + previous.end = new Date(end); + } else { + blocks.push({ start: new Date(start), end: new Date(end), title, description }); + } + } + + return blocks; +} diff --git a/frontend/src/pages/calendar.tsx b/frontend/src/pages/calendar.tsx index d8806a4..b7d77a8 100644 --- a/frontend/src/pages/calendar.tsx +++ b/frontend/src/pages/calendar.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from "react"; import { EventCalendar } from "@/components/event-calendar"; +import { CalendarBlockLabel } from "@/components/calendar-block-label"; import svLocale from "@fullcalendar/react/locales/sv"; import enLocale from "@fullcalendar/react/locales/en-gb"; import { addDays, startOfDay, startOfWeek } from "date-fns"; @@ -16,6 +17,7 @@ import { } from "@/generated/graphql"; import { rooms } from "@/lib/rooms"; import { bookingRoomClass, bookingRoomStyles } from "@/lib/booking-colors"; +import { calendarBlocks } from "@/lib/calendar-blocks"; import { parseDate, localInput } from "@/lib/dates"; import { useLanguage } from "@/lib/language"; import { useUser } from "@/lib/user"; @@ -140,6 +142,17 @@ export function CalendarPage() { slotHeaderFormat={{ hour: "2-digit", minute: "2-digit", hour12: false }} dayHeaderFormat={{ weekday: "short", day: "2-digit", month: "2-digit" }} views={{ dayGridMonth: { dayHeaderFormat: { weekday: "short" }, dayMaxEvents: 3 } }} + backgroundEventInnerClass="overflow-hidden min-w-0" + backgroundEventContent={({ event }) => ( + <CalendarBlockLabel + title={event.title} + description={ + typeof event.extendedProps.description === "string" + ? event.extendedProps.description + : event.title + } + /> + )} datesSet={(info) => setRange((current) => current.from === info.start.toISOString() && current.to === info.end.toISOString() @@ -185,15 +198,15 @@ export function CalendarPage() { editable: Boolean(user?.is_admin || user?.groups?.includes(event.booked_as)), durationEditable: false, })), - ...(query.data?.illegalSlots ?? []) - .filter((slot) => slot.room.some((room) => selectedRooms.includes(room))) - .map((slot) => ({ - start: parseDate(slot.start), - end: parseDate(slot.end), - display: "background", - color: "#EF9A9A", - title: slot.title + (slot.description ? ` — ${slot.description}` : ""), - })), + ...calendarBlocks( + (query.data?.illegalSlots ?? []).filter((slot) => + slot.room.some((room) => selectedRooms.includes(room)), + ), + ).map((block) => ({ + ...block, + display: "background", + color: "#EF9A9A", + })), ...(new Date(range.to) > startOfDay(addDays(new Date(), 63)) ? [ { From 05acf95e35b800193a1a9f925a058b5370574cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 11:23:00 +0200 Subject: [PATCH 08/41] Install the pinned pnpm version in CI jobs --- .github/workflows/ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a9d8d7..52e00de 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,6 +32,10 @@ jobs: version: "0.3.0" run-install: false cache: true + - name: Install the packageManager-pinned pnpm + uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 + with: + run_install: false - name: Install approved dependencies from the lockfile run: vp install --frozen-lockfile - name: Generate GraphQL contracts and check they are committed @@ -66,6 +70,10 @@ jobs: version: "0.3.0" run-install: false cache: true + - name: Install the packageManager-pinned pnpm + uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 + with: + run_install: false - run: vp install --frozen-lockfile - run: pnpm codegen - name: Install Chromium and its system dependencies From 7c8f47e85ea1eeb20bbb6f1d375ada3028ccce9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 11:23:00 +0200 Subject: [PATCH 09/41] Allow authorized edits of anonymized bookings with a new contact --- README.md | 4 + backend/src/services/event.service.ts | 7 +- backend/src/tests/booking-transaction.test.ts | 79 ++++++++++++++++++- frontend/src/components/booking-form.tsx | 4 +- 4 files changed, 87 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f27e92a..01947f3 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,10 @@ An authorized group edit may omit the phone number to preserve it; new bookings require one. Edits preserve the original author, and overlapping booking writes are checked within a serializable transaction. +If privacy cleanup has removed a booking's author, an authorized edit requires a +new phone number and records the editor as the new contact. It never restores the +deleted author's identity or reuses an unowned phone number. + The privacy cleanup retains the existing SQL that clears phone numbers and booking-user IDs 14 days after an event. Its unsupported Jobber image was replaced with a small PostgreSQL-client container: cleanup runs on startup and every 24 diff --git a/backend/src/services/event.service.ts b/backend/src/services/event.service.ts index bbe1b0d..4b1fc51 100644 --- a/backend/src/services/event.service.ts +++ b/backend/src/services/event.service.ts @@ -247,10 +247,13 @@ export const editEvent = async ( // Group members may edit bookings without access to the author's phone. // Keep both fields together: changing the author would grant the editor // access to a phone number that belonged to someone else. + // After privacy cleanup removes the author, require a fresh phone number + // and make the authenticated editor the new contact. Never reclaim an old + // phone number whose owner is unknown. const updated = { ...event, - phone: event.phone ?? previous.phone, - booked_by: previous.booked_by, + phone: event.phone ?? (previous.booked_by ? previous.phone : ""), + booked_by: previous.booked_by || user.cid, }; const error = (await validEvent(transaction, updated, user)) || (await checkRules(transaction, updated)); diff --git a/backend/src/tests/booking-transaction.test.ts b/backend/src/tests/booking-transaction.test.ts index 74c2167..8a15168 100644 --- a/backend/src/tests/booking-transaction.test.ts +++ b/backend/src/tests/booking-transaction.test.ts @@ -51,8 +51,8 @@ describe("Booking transaction recovery", () => { booking_terms: true, room: ["GROUP_ROOM"], }; - it.each([undefined, null])( - "preserves the author's phone and identity when edit phone is %s", + it.each([undefined, null, "0707654321"])( + "preserves the author's identity and handles edit phone %s", async (phone) => { const previous = { id: "booking", @@ -76,7 +76,7 @@ describe("Booking transaction recovery", () => { expect(update).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ - phone: "0701234567", + phone: phone ?? "0701234567", booked_by: "author", title: "Edited booking", }), @@ -84,6 +84,79 @@ describe("Booking transaction recovery", () => { ); }, ); + const anonymized = { + id: "booking", + title: "Old booking", + description: "", + booked_by: "", + booked_as: "digit", + phone: "", + room: ["GROUP_ROOM"], + start: new Date(Date.now() - 21 * 86_400_000), + end: new Date(Date.now() - 20 * 86_400_000), + created_at: new Date(Date.now() - 30 * 86_400_000), + updated_at: new Date(Date.now() - 30 * 86_400_000), + }; + + it.each([false, true])( + "lets an authorized editor become the contact for an anonymized booking (admin: %s)", + async (is_admin) => { + vi.spyOn(prisma, "$transaction").mockImplementation(async (operation) => operation(prisma)); + vi.spyOn(prisma.event, "findUnique").mockResolvedValue(anonymized); + vi.spyOn(prisma.event, "count").mockResolvedValue(0); + vi.spyOn(prisma.rule, "findMany").mockResolvedValue([]); + const update = vi.spyOn(prisma.event, "update").mockResolvedValue(anonymized); + + expect( + await editEvent( + prisma, + { ...input, phone: "0707654321", booked_by: "untrusted-author" }, + { ...user, is_admin, groups: is_admin ? [] : user.groups }, + ), + ).toBeNull(); + expect(update).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ + booked_by: user.cid, + phone: "0707654321", + start: new Date(input.start), + end: new Date(input.end), + }), + }), + ); + }, + ); + + it.each([undefined, null, "", "invalid"])( + "requires a fresh valid phone for an anonymized booking, not %s", + async (phone) => { + vi.spyOn(prisma, "$transaction").mockImplementation(async (operation) => operation(prisma)); + // Even inconsistent legacy data must not expose an unowned phone number. + vi.spyOn(prisma.event, "findUnique").mockResolvedValue({ + ...anonymized, + phone: "0701234567", + }); + vi.spyOn(prisma.event, "count").mockResolvedValue(0); + const update = vi.spyOn(prisma.event, "update"); + + expect(await editEvent(prisma, { ...input, phone }, user)).toMatchObject({ + en: "Provided phone number is faulty", + }); + expect(update).not.toHaveBeenCalled(); + }, + ); + + it("does not let an outsider take over an anonymized booking", async () => { + vi.spyOn(prisma, "$transaction").mockImplementation(async (operation) => operation(prisma)); + vi.spyOn(prisma.event, "findUnique").mockResolvedValue(anonymized); + const update = vi.spyOn(prisma.event, "update"); + + expect( + await editEvent(prisma, { ...input, phone: "0707654321" }, { ...user, groups: [] }), + ).toMatchObject({ en: "You do not have permission to edit this event" }); + expect(update).not.toHaveBeenCalled(); + }); + it("still rejects new bookings without a phone", async () => { vi.spyOn(prisma, "$transaction").mockImplementation(async (operation) => operation(prisma)); vi.spyOn(prisma.event, "count").mockResolvedValue(0); diff --git a/frontend/src/components/booking-form.tsx b/frontend/src/components/booking-form.tsx index f3df9e6..4f15f1b 100644 --- a/frontend/src/components/booking-form.tsx +++ b/frontend/src/components/booking-form.tsx @@ -138,11 +138,11 @@ export function BookingForm({ name="phone" type="tel" autoComplete="tel" - required={!booking} + required={!booking?.booked_by} pattern={String.raw`\+?\(?[0-9]{3}\)?[\-\s.]?[0-9]{3}[\-\s.]?[0-9]{4,5}`} defaultValue={booking?.phone ?? ""} placeholder={ - booking + booking?.booked_by ? t("Leave blank to keep existing number", "Lämna tomt för att behålla numret") : "070 123 45 67" } From 04c659d8b10eb429bfde27cca1286cb2614a60f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 11:33:42 +0200 Subject: [PATCH 10/41] Run CI E2E against published candidate image digests --- .github/workflows/ci.yml | 132 +++++++++++++++++++----- README.md | 8 +- e2e/README.md | 72 ++++++++++++- e2e/application-images.test.ts | 49 +++++++++ e2e/application-images.ts | 30 ++++++ e2e/compose.ts | 180 +++++++++++++++++++++------------ e2e/screenshots.ts | 2 +- e2e/stress.ts | 2 +- vite.config.ts | 5 +- 9 files changed, 386 insertions(+), 94 deletions(-) create mode 100644 e2e/application-images.test.ts create mode 100644 e2e/application-images.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 52e00de..3b8677f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,12 +54,76 @@ jobs: run: pnpm build - name: Audit production and development dependencies run: pnpm audit --audit-level low + - name: Explain restricted PR image coverage + if: github.event_name == 'pull_request' && (github.event.pull_request.head.repo.full_name != github.repository || github.actor == 'dependabot[bot]') + run: echo "Image publishing and image E2E are skipped for fork/Dependabot PRs. After reviewing the changes, a maintainer must put them on a trusted branch in this repository to run the image pipeline. No registry credentials are supplied to this job." >> "$GITHUB_STEP_SUMMARY" - e2e: - name: Browser flows with real Gamma, PostgreSQL, and Redis + candidates: + name: Publish candidate images needs: quality + if: github.event_name != 'pull_request' || (github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]') + runs-on: ubuntu-24.04 + timeout-minutes: 30 + permissions: + contents: read + packages: write + outputs: + frontend: ${{ steps.names.outputs.frontend }}@${{ steps.frontend.outputs.digest }} + backend: ${{ steps.names.outputs.backend }}@${{ steps.backend.outputs.digest }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 + - name: Candidate names + id: names + shell: bash + run: | + repository="ghcr.io/${GITHUB_REPOSITORY,,}" + echo "frontend=${repository}-frontend" >> "$GITHUB_OUTPUT" + echo "backend=${repository}-backend" >> "$GITHUB_OUTPUT" + echo "tag=candidate-${GITHUB_SHA}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" >> "$GITHUB_OUTPUT" + - name: Log in to GHCR for candidate publication + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Publish frontend candidate + id: frontend + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 + with: + context: . + file: frontend/Dockerfile + platforms: linux/amd64 + push: true + tags: ${{ steps.names.outputs.frontend }}:${{ steps.names.outputs.tag }} + cache-from: type=gha,scope=frontend + cache-to: type=gha,mode=max,scope=frontend + - name: Publish backend candidate + id: backend + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 + with: + context: . + file: backend/Dockerfile + platforms: linux/amd64 + push: true + tags: ${{ steps.names.outputs.backend }}:${{ steps.names.outputs.tag }} + cache-from: type=gha,scope=backend + cache-to: type=gha,mode=max,scope=backend + + e2e: + name: Browser flows against published candidates + needs: candidates runs-on: ubuntu-24.04 timeout-minutes: 30 + permissions: + contents: read + packages: read + env: + E2E_MODE: images + BOOKIT_FRONTEND_IMAGE: ${{ needs.candidates.outputs.frontend }} + BOOKIT_BACKEND_IMAGE: ${{ needs.candidates.outputs.backend }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -80,8 +144,20 @@ jobs: run: pnpm exec playwright install --with-deps chromium - name: Verify Docker is available for Testcontainers run: docker info + - name: Log in to GHCR for candidate pulls + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Run authenticated browser flows - run: pnpm test:e2e + shell: bash + run: pnpm test:e2e 2>&1 | tee e2e-run.log + - name: Dry-run digest promotion + shell: bash + run: | + docker buildx imagetools create --dry-run --prefer-index=false "$BOOKIT_FRONTEND_IMAGE" > /dev/null + docker buildx imagetools create --dry-run --prefer-index=false "$BOOKIT_BACKEND_IMAGE" > /dev/null - name: Preserve failing browser traces, screenshots, videos, and logs if: failure() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -90,41 +166,45 @@ jobs: path: | playwright-report/ test-results/ + e2e-run.log if-no-files-found: ignore retention-days: 7 - containers: - name: Build ${{ matrix.component }} container - needs: [quality, e2e] + promote: + name: Promote tested digests on main + needs: [candidates, e2e] + if: github.event_name == 'push' && github.ref == 'refs/heads/main' runs-on: ubuntu-24.04 timeout-minutes: 30 permissions: contents: read packages: write - strategy: - fail-fast: false - matrix: - component: [backend, frontend] + env: + BOOKIT_FRONTEND_IMAGE: ${{ needs.candidates.outputs.frontend }} + BOOKIT_BACKEND_IMAGE: ${{ needs.candidates.outputs.backend }} steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - name: Log in to GitHub Container Registry - if: github.event_name == 'push' && github.ref == 'refs/heads/main' uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: registry: ghcr.io - username: ${{ github.repository_owner }} + username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Build image and publish successful main builds - uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 - with: - context: . - file: ${{ matrix.component }}/Dockerfile - push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} - tags: | - ghcr.io/cthit/bookit-node-${{ matrix.component }}:latest - ghcr.io/cthit/bookit-node-${{ matrix.component }}:${{ github.sha }} - cache-from: type=gha,scope=${{ matrix.component }} - cache-to: type=gha,mode=max,scope=${{ matrix.component }} + - name: Refuse to promote an outdated main run + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + shell: bash + run: test "$(gh api "repos/$GITHUB_REPOSITORY/git/ref/heads/main" --jq .object.sha)" = "$GITHUB_SHA" + - name: Tag the tested manifests without rebuilding + shell: bash + run: | + for image in "$BOOKIT_FRONTEND_IMAGE" "$BOOKIT_BACKEND_IMAGE"; do + repository="${image%@*}" + digest="${image##*@}" + docker buildx imagetools create --prefer-index=false \ + --tag "$repository:$GITHUB_SHA" --tag "$repository:latest" "$image" + for tag in "$GITHUB_SHA" latest; do + actual="$(docker buildx imagetools inspect "$repository:$tag" --format '{{.Manifest.Digest}}')" + test "$actual" = "$digest" + done + done diff --git a/README.md b/README.md index 01947f3..6592d06 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,13 @@ One `e2e/compose.ts` owns all Testcontainers orchestration and separate database CI enforces contracts, types, lint/format, unit/API tests, builds, dependency audit, the browser suite, and container builds. Browser failure artifacts are retained. -Successful main builds publish the existing GHCR frontend/backend image names. +After quality checks, CI publishes uniquely tagged GHCR candidates and runs E2E +against their immutable digests. Successful main runs promote those same digests +to the existing commit-SHA and latest tags without rebuilding. PR runs never +update production tags. Fork/Dependabot PRs run quality checks only until reviewed +and moved to a trusted branch; they receive no registry credentials. +See [image-mode E2E](e2e/README.md#development-servers-or-published-images) for local +digest-based runs, routing, explicit schema initialization, and registry access. Actions are SHA-pinned and Dependabot tracks future updates. ## Containers and deployment diff --git a/e2e/README.md b/e2e/README.md index c1a1b90..3b5c089 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -3,7 +3,7 @@ The suite exercises the real BookIT backend and frontend against a real Gamma identity provider. `compose.ts` owns the complete environment: an isolated Docker network, two PostgreSQL containers, two Redis containers, Gamma, Prisma schema -initialization, and the application processes. Ports are assigned per run. It +initialization, and either application processes or published application containers. Ports are assigned per run. It does not read another checkout or use a developer's database or credentials. Install dependencies and Playwright Chromium, then use `pnpm test:e2e`. Docker @@ -11,6 +11,76 @@ must be running. A worker shares the container environment, while each test gets a fresh browser context and cleared BookIT bookings/rules. Run with one worker to keep the suite's resource usage predictable. +## Development servers or published images + +Local runs default to development servers (`E2E_MODE=dev`), including +`pnpm e2e:dev`. CI requires `E2E_MODE=images` and refuses to fall back to +development servers if either image is missing. + +To run the same suite locally against specific published candidates: + +```sh +# For private packages, authenticate Docker with read:packages access first. +docker login ghcr.io +E2E_MODE=images \ +BOOKIT_FRONTEND_IMAGE='ghcr.io/cthit/bookit-node-frontend@sha256:<frontend-digest>' \ +BOOKIT_BACKEND_IMAGE='ghcr.io/cthit/bookit-node-backend@sha256:<backend-digest>' \ +pnpm test:e2e +``` + +Replace each placeholder with its 64-character digest from the CI candidate job +outputs/logs. Tags such as `latest` are rejected. Docker/Testcontainers use the +host's Docker credential store; credentials are not forwarded into containers. +Public images do not require a login. Use the same variables with `pnpm e2e:dev` +to keep a published-image environment open for manual inspection. + +Image mode runs the exact backend digest once with `prisma db push` against the +empty, isolated BookIT database, and checks its exit status before starting the +application. It does not generate a client from the checkout or migrate on normal +backend startup. No production database is touched. + +The browser connects to the backend's mapped port. Backend API routes stay under +`/api`; all other requests proxy to the Nginx frontend container on port 80, as +in production Compose. Both BookIT containers retain their production commands +and the backend runs with `NODE_ENV=production`. Images target `linux/amd64`; +Docker Desktop on Apple Silicon runs them under emulation. + +In image mode Gamma uses `gamma.localhost:<port>` as its single OIDC issuer: +Chromium resolves it to host loopback, while the backend resolves the identical +name through a Docker network alias. Gamma listens on that port inside its +container too. No issuer rewriting, authentication mocks, or development proxy +are involved. + +## CI image lifecycle and fork PRs + +Quality checks run first. Trusted runs then publish frontend/backend candidates +to GHCR, tagged `candidate-<commit>-<run-id>-<attempt>`. PRs use GitHub's checked-out +merge commit, so the candidates contain exactly the source that quality checks +examined. Build outputs pass immutable `repository@sha256:...` references to E2E. +The candidate and E2E jobs have only package-write and package-read permissions, +respectively, plus checkout read access; login credentials are cleaned up by the +login action. + +After image E2E passes on a push to main, a separate job copies those manifests to +the commit-SHA and `latest` tags and verifies the resulting digests. It never +rebuilds. It refuses promotion if main has advanced. PRs and manual dispatches +publish candidates only and never update production tags. E2E also dry-runs the +manifest-copy operation without writing registry tags. + +Fork PRs and Dependabot PRs run quality checks only. They receive no registry +credentials and skip candidate publication/image E2E. After reviewing their code, +a maintainer must place it on a trusted branch in this repository to get image +coverage before merging. We do not use `pull_request_target` to execute PR code. +GHCR packages must grant this repository's Actions token write/read access; this +may need configuring for pre-existing packages. + +Candidate tags are retained for debugging; apply a registry retention policy +separately without removing digests referenced by production tags. Failures retain +the E2E console log (including image references and startup logs), browser traces, +screenshots, video, and bounded per-container logs. Normal completion and failure +both stop the owned containers/network; Testcontainers' resource reaper handles +abrupt runner termination. + The spec files contain focused, independent tests rather than one long test per file. Booking lifecycle and concurrency, calendar navigation and drag/edit consistency, language persistence, mobile layout, rule validation/lifecycle, and diff --git a/e2e/application-images.test.ts b/e2e/application-images.test.ts new file mode 100644 index 0000000..aad03dc --- /dev/null +++ b/e2e/application-images.test.ts @@ -0,0 +1,49 @@ +import { describe, expect, it } from "vite-plus/test"; +import { applicationImages } from "./application-images"; + +const frontend = `ghcr.io/cthit/bookit-node-frontend@sha256:${"a".repeat(64)}`; +const backend = `ghcr.io/cthit/bookit-node-backend@sha256:${"b".repeat(64)}`; + +describe("E2E application mode", () => { + it("defaults to local development servers", () => { + expect(applicationImages({})).toBeUndefined(); + }); + + it("requires published-image mode in CI", () => { + expect(() => applicationImages({ CI: "true" })).toThrow("CI E2E requires"); + }); + + it("accepts immutable frontend and backend references", () => { + expect( + applicationImages({ + E2E_MODE: "images", + CI: "true", + BOOKIT_FRONTEND_IMAGE: frontend, + BOOKIT_BACKEND_IMAGE: backend, + }), + ).toEqual({ frontend, backend }); + }); + + it.each([undefined, "ghcr.io/cthit/bookit-node-backend:latest", "backend@sha256:abc"])( + "rejects missing or mutable image references: %s", + (value) => { + expect(() => + applicationImages({ + E2E_MODE: "images", + BOOKIT_FRONTEND_IMAGE: frontend, + BOOKIT_BACKEND_IMAGE: value, + }), + ).toThrow("BOOKIT_BACKEND_IMAGE must be"); + }, + ); + + it("does not silently ignore image references in development mode", () => { + expect(() => applicationImages({ BOOKIT_FRONTEND_IMAGE: frontend })).toThrow( + "Set E2E_MODE=images", + ); + }); + + it("rejects unknown modes", () => { + expect(() => applicationImages({ E2E_MODE: "production" })).toThrow("E2E_MODE must be"); + }); +}); diff --git a/e2e/application-images.ts b/e2e/application-images.ts new file mode 100644 index 0000000..7370d5e --- /dev/null +++ b/e2e/application-images.ts @@ -0,0 +1,30 @@ +export interface ApplicationImages { + frontend: string; + backend: string; +} + +export function applicationImages( + env: NodeJS.ProcessEnv = process.env, +): ApplicationImages | undefined { + const mode = env.E2E_MODE ?? "dev"; + if (mode !== "dev" && mode !== "images") throw new Error("E2E_MODE must be dev or images"); + if (env.CI && mode !== "images") throw new Error("CI E2E requires E2E_MODE=images"); + + if (mode === "dev") { + if (env.BOOKIT_FRONTEND_IMAGE || env.BOOKIT_BACKEND_IMAGE) + throw new Error("Set E2E_MODE=images when supplying BookIT image references"); + return undefined; + } + + const digestImage = (name: string): string => { + const value = env[name]; + if (!value || !/^[a-z0-9][a-z0-9._:/-]*@sha256:[a-f0-9]{64}$/.test(value)) + throw new Error(`${name} must be a registry image pinned with @sha256:<64 hex characters>`); + return value; + }; + + return { + frontend: digestImage("BOOKIT_FRONTEND_IMAGE"), + backend: digestImage("BOOKIT_BACKEND_IMAGE"), + }; +} diff --git a/e2e/compose.ts b/e2e/compose.ts index 45098c0..74deead 100644 --- a/e2e/compose.ts +++ b/e2e/compose.ts @@ -5,6 +5,7 @@ import { spawn, type ChildProcess } from "node:child_process"; import { createServer, type AddressInfo } from "node:net"; import { resolve } from "node:path"; import { setTimeout as delay } from "node:timers/promises"; +import { applicationImages } from "./application-images"; const root = resolve(__dirname, ".."); @@ -178,6 +179,8 @@ export async function loginAs( } export async function compose(browser: Browser): Promise<Environment> { + // Validate before allocating any containers; CI must never fall back to dev servers. + const appImages = applicationImages(); const network = await new Network().start(); const containers: StartedTestContainer[] = []; const processes: ChildProcess[] = []; @@ -288,7 +291,11 @@ export async function compose(browser: Browser): Promise<Environment> { ); const gammaPort = await unusedPort(); - const gammaUrl = `http://localhost:${gammaPort}`; + // Chromium resolves *.localhost to loopback. Docker resolves the same name + // through Gamma's network alias, keeping the OIDC issuer identical on both sides. + const gammaHost = appImages ? "gamma.localhost" : "localhost"; + const gammaUrl = `http://${gammaHost}:${gammaPort}`; + const gammaContainerPort = appImages ? gammaPort : 8081; const appPort = await unusedPort(); const appUrl = `http://localhost:${appPort}`; const frontendPort = await unusedPort(); @@ -298,14 +305,14 @@ export async function compose(browser: Browser): Promise<Environment> { .withPlatform("linux/amd64") .withResourcesQuota({ memory: 2 }) .withNetwork(network) - .withNetworkAliases("gamma") + .withNetworkAliases("gamma", "gamma.localhost") .withEnvironment({ DB_HOST: "gamma-db", DB_NAME: gammaDb.getDatabase(), DB_USER: gammaDb.getUsername(), DB_PASSWORD: gammaDb.getPassword(), REDIS_HOST: "gamma-redis", - SERVER_PORT: "8081", + SERVER_PORT: String(gammaContainerPort), BASE_URL: gammaUrl, PRODUCTION: "false", IS_MOCKING: "true", @@ -316,11 +323,11 @@ export async function compose(browser: Browser): Promise<Environment> { .withCopyContentToContainer([ { content: JSON.stringify(seed), target: "/tmp/bookit-gamma-seed.json" }, ]) - .withExposedPorts({ container: 8081, host: gammaPort }) + .withExposedPorts({ container: gammaContainerPort, host: gammaPort }) .withLogConsumer((stream) => stream.on("data", (chunk: Buffer) => record("gamma", chunk))) .withWaitStrategy( Wait.forAll([ - Wait.forHttp("/login", 8081).forStatusCode(200), + Wait.forHttp("/login", gammaContainerPort).forStatusCode(200), Wait.forLogMessage(/Api key of type INFO has been generated/), ]), ) @@ -332,81 +339,128 @@ export async function compose(browser: Browser): Promise<Environment> { const client = await provisionClient(browser, gammaUrl, appUrl); const env = { - NODE_ENV: "test", + NODE_ENV: appImages ? "production" : "test", TZ: "Europe/Stockholm", - DATABASE_URL: bookitDb.getConnectionUri(), - REDIS_HOST: bookitRedis.getHost(), - REDIS_PORT: String(bookitRedis.getMappedPort(6379)), + DATABASE_URL: appImages + ? "postgresql://bookit_test:bookit_test@bookit-db:5432/bookit_test" + : bookitDb.getConnectionUri(), + REDIS_HOST: appImages ? "bookit-redis" : bookitRedis.getHost(), + REDIS_PORT: appImages ? "6379" : String(bookitRedis.getMappedPort(6379)), REDIS_PASS: "", SESSION_SECRET: "bookit-isolated-e2e-session-secret", SECRET: "bookit-isolated-e2e-oidc-secret-at-least-32-characters", BASE_URL: appUrl, BACKEND_URL: appUrl, - PORT: String(appPort), - FRONTEND_URL: `http://127.0.0.1:${frontendPort}`, + PORT: appImages ? "8080" : String(appPort), + FRONTEND_URL: appImages ? "http://bookit-frontend:80" : `http://127.0.0.1:${frontendPort}`, ISSUER_BASE_URL: gammaUrl, CLIENT_ID: client.clientId, CLIENT_SECRET: client.clientSecret, API_KEY: client.apiKey, }; - console.log("Generating Prisma client and initializing the isolated BookIT schema..."); - for (const command of [["generate"], ["db", "push"]]) { - const migrate = startProcess( - "schema", - ["--dir", "backend", "exec", "prisma", ...command], - env, + if (appImages) { + record("images", JSON.stringify(appImages)); + console.log(`Using published BookIT images: ${JSON.stringify(appImages)}`); + console.log("Initializing the isolated database with the candidate backend image..."); + await track( + new GenericContainer(appImages.backend) + .withPlatform("linux/amd64") + .withNetwork(network) + .withEnvironment({ DATABASE_URL: env.DATABASE_URL }) + .withCommand(["./node_modules/.bin/prisma", "db", "push"]) + .withLogConsumer((stream) => + stream.on("data", (chunk: Buffer) => record("schema", chunk)), + ) + .withWaitStrategy(Wait.forOneShotStartup()) + .withStartupTimeout(120_000) + .start(), + ); + await track( + new GenericContainer(appImages.frontend) + .withPlatform("linux/amd64") + .withNetwork(network) + .withNetworkAliases("bookit-frontend") + .withExposedPorts(80) + .withLogConsumer((stream) => + stream.on("data", (chunk: Buffer) => record("frontend", chunk)), + ) + .withWaitStrategy(Wait.forHttp("/", 80).forStatusCode(200)) + .withStartupTimeout(120_000) + .start(), + ); + await track( + new GenericContainer(appImages.backend) + .withPlatform("linux/amd64") + .withNetwork(network) + .withEnvironment(env) + .withExposedPorts({ container: 8080, host: appPort }) + .withLogConsumer((stream) => + stream.on("data", (chunk: Buffer) => record("backend", chunk)), + ) + .withWaitStrategy(Wait.forHttp("/api/health", 8080).forStatusCode(200)) + .withStartupTimeout(120_000) + .start(), ); - await new Promise<void>((resolve, reject) => { - migrate.once("error", reject); - migrate.once("exit", (code) => - code === 0 - ? resolve() - : reject(new Error(`BookIT schema setup failed: ${logs.schema ?? ""}`)), + } else { + console.log("Generating Prisma client and initializing the isolated BookIT schema..."); + for (const command of [["generate"], ["db", "push"]]) { + const migrate = startProcess( + "schema", + ["--dir", "backend", "exec", "prisma", ...command], + env, ); - }); - } + await new Promise<void>((resolve, reject) => { + migrate.once("error", reject); + migrate.once("exit", (code) => + code === 0 + ? resolve() + : reject(new Error(`BookIT schema setup failed: ${logs.schema ?? ""}`)), + ); + }); + } - const frontend = startProcess( - "frontend", - [ - "--dir", + const frontend = startProcess( "frontend", - "dev", - "--host", - "127.0.0.1", - "--port", - String(frontendPort), - "--strictPort", - ], - env, - ); - const backend = startProcess("backend", ["--dir", "backend", "start"], env); - - await expect - .poll( - async () => { - for (const child of [frontend, backend]) { - if (child.exitCode !== null || child.signalCode !== null) { - throw new Error( - `BookIT exited during startup.\n${logs.backend ?? ""}\n${logs.frontend ?? ""}`, - ); + [ + "--dir", + "frontend", + "dev", + "--host", + "127.0.0.1", + "--port", + String(frontendPort), + "--strictPort", + ], + env, + ); + const backend = startProcess("backend", ["--dir", "backend", "start"], env); + + await expect + .poll( + async () => { + for (const child of [frontend, backend]) { + if (child.exitCode !== null || child.signalCode !== null) { + throw new Error( + `BookIT exited during startup.\n${logs.backend ?? ""}\n${logs.frontend ?? ""}`, + ); + } } - } - const front = await fetch(env.FRONTEND_URL, { signal: AbortSignal.timeout(2_000) }).catch( - () => undefined, - ); - await front?.body?.cancel(); - const back = await fetch(`${appUrl}/api/health`, { - redirect: "manual", - signal: AbortSignal.timeout(2_000), - }).catch(() => undefined); - await back?.body?.cancel(); - return front?.status === 200 && back?.status === 200; - }, - { timeout: 120_000, intervals: [500, 1000] }, - ) - .toBe(true); + const front = await fetch(env.FRONTEND_URL, { + signal: AbortSignal.timeout(2_000), + }).catch(() => undefined); + await front?.body?.cancel(); + const back = await fetch(`${appUrl}/api/health`, { + redirect: "manual", + signal: AbortSignal.timeout(2_000), + }).catch(() => undefined); + await back?.body?.cancel(); + return front?.status === 200 && back?.status === 200; + }, + { timeout: 120_000, intervals: [500, 1000] }, + ) + .toBe(true); + } return { appUrl, diff --git a/e2e/screenshots.ts b/e2e/screenshots.ts index f7cdc8b..50b1dd4 100644 --- a/e2e/screenshots.ts +++ b/e2e/screenshots.ts @@ -8,7 +8,7 @@ async function main() { if (!appUrl || !gammaUrl || !outputDirectory) throw new Error("Provide local app URL, Gamma URL and output directory"); for (const url of [appUrl, gammaUrl]) { - if (!["localhost", "127.0.0.1"].includes(new URL(url).hostname)) + if (!["localhost", "127.0.0.1", "gamma.localhost"].includes(new URL(url).hostname)) throw new Error("Only localhost previews are supported"); } await mkdir(outputDirectory, { recursive: true }); diff --git a/e2e/stress.ts b/e2e/stress.ts index 1752ed9..94e3fca 100644 --- a/e2e/stress.ts +++ b/e2e/stress.ts @@ -7,7 +7,7 @@ async function main() { if (!appUrl || !gammaUrl) throw new Error("Usage: pnpm exec tsx e2e/stress.ts <local app URL> <local Gamma URL>"); for (const url of [appUrl, gammaUrl]) { - if (!["localhost", "127.0.0.1"].includes(new URL(url).hostname)) { + if (!["localhost", "127.0.0.1", "gamma.localhost"].includes(new URL(url).hostname)) { throw new Error("Stress fixtures may only be added to a localhost test environment"); } } diff --git a/vite.config.ts b/vite.config.ts index 11bd1b2..16b9618 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,6 +1,9 @@ import { defineConfig } from "vite-plus"; export default defineConfig({ - test: { include: ["backend/src/**/*.test.ts", "frontend/src/**/*.test.ts"], environment: "node" }, + test: { + include: ["backend/src/**/*.test.ts", "frontend/src/**/*.test.ts", "e2e/**/*.test.ts"], + environment: "node", + }, lint: { plugins: ["typescript", "react", "jsx-a11y", "import", "unicorn", "oxc"], overrides: [ From 0e24b446220e68213b58c9d2cd5d54da5e9834ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 11:43:59 +0200 Subject: [PATCH 11/41] Remove obsolete setup files and redundant comments --- .gitignore | 2 + README.md | 9 +- backend/.dockerignore | 2 - backend/Dockerfile | 1 - backend/dev_setup.sh | 10 - backend/src/auth/session-store.ts | 2 - backend/src/index.ts | 4 +- backend/src/services/event.service.ts | 18 +- backend/src/services/rule.service.ts | 4 +- backend/src/tests/booking-transaction.test.ts | 3 - backend/src/tests/graphql.test.ts | 3 +- backend/startup.sh | 2 - codegen.ts | 1 - db-scripts/cleanup.sh | 1 - e2e/booking-helpers.ts | 1 - e2e/booking-lifecycle.spec.ts | 2 - e2e/calendar.spec.ts | 3 +- e2e/compose.ts | 10 +- e2e/screenshots.ts | 1 - e2e/stress-helpers.ts | 1 - e2e/stress.spec.ts | 2 - frontend/.dockerignore | 2 - frontend/.env | 2 - frontend/.gitignore | 23 --- frontend/Dockerfile | 1 - frontend/dev_setup.sh | 2 - frontend/public/manifest.json | 15 -- frontend/src/components/date-time-fields.tsx | 2 - frontend/src/components/ui/alert.tsx | 59 ------ .../components/ui/event-calendar-views.tsx | 105 ++--------- frontend/src/components/ui/select.tsx | 174 ------------------ frontend/src/components/ui/separator.tsx | 27 --- frontend/src/components/ui/skeleton.tsx | 13 -- frontend/src/lib/booking-colors.ts | 2 - frontend/src/lib/calendar-blocks.ts | 3 +- frontend/src/pages/rules.tsx | 1 - frontend/src/router.tsx | 1 - pnpm-workspace.yaml | 3 +- prod.docker-compose.yml | 1 - queries.md | 43 ----- 40 files changed, 28 insertions(+), 533 deletions(-) delete mode 100644 backend/.dockerignore delete mode 100755 backend/dev_setup.sh delete mode 100644 frontend/.dockerignore delete mode 100644 frontend/.env delete mode 100644 frontend/.gitignore delete mode 100755 frontend/dev_setup.sh delete mode 100644 frontend/public/manifest.json delete mode 100644 frontend/src/components/ui/alert.tsx delete mode 100644 frontend/src/components/ui/select.tsx delete mode 100644 frontend/src/components/ui/separator.tsx delete mode 100644 frontend/src/components/ui/skeleton.tsx delete mode 100644 queries.md diff --git a/.gitignore b/.gitignore index 9f2e3fd..93d2525 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ dist/ playwright-report/ test-results/ coverage/ +.DS_Store +*.log diff --git a/README.md b/README.md index 6592d06..59a867f 100644 --- a/README.md +++ b/README.md @@ -79,11 +79,6 @@ GraphQL Codegen core API generates backend resolver signatures and typed fronten operation documents from this schema and `frontend/src/api/operations.graphql`. Both generated outputs are committed; CI rejects stale output. -The redesigned interface keeps the original room filters, multi-room booking colors, -calendar selection and drag-to-move behavior, booking details dialog, group selection, -consent fields, Swedish/English labels, and sortable rules table. Room filters remain -compact so the calendar is the main view; decorative headings are intentionally omitted. - ## Browser verification Complete a manual browser smoke pass before running the automated flows: @@ -95,8 +90,8 @@ pnpm e2e:dev pnpm test:e2e ``` -Four separate Playwright specs cover booking persistence, calendar filters and -navigation, administrator rules, and group/outsider authorization. The extended +Playwright specs cover booking persistence, calendar filters and navigation, +administrator rules, authorization, accessibility, and busy calendars. The extended test fixture authenticates a fresh browser context by default, with configurable admin/member/outsider identities. It uses real Gamma rather than mocked login. One `e2e/compose.ts` owns all Testcontainers orchestration and separate databases. diff --git a/backend/.dockerignore b/backend/.dockerignore deleted file mode 100644 index dd87e2d..0000000 --- a/backend/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -build diff --git a/backend/Dockerfile b/backend/Dockerfile index c8105ad..816d784 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,3 @@ -# Build from the repository root: docker build -f backend/Dockerfile . FROM node:24.19.0-bookworm-slim AS base ENV COREPACK_HOME=/opt/corepack RUN apt-get update && apt-get install -y --no-install-recommends openssl ca-certificates && apt-get clean diff --git a/backend/dev_setup.sh b/backend/dev_setup.sh deleted file mode 100755 index ae22989..0000000 --- a/backend/dev_setup.sh +++ /dev/null @@ -1,10 +0,0 @@ -export DB_USER=postgres -export DB_PASS=password -export DB_HOST=localhost -export DB_PORT=5432 -export DB_NAME=bookit - -export DATABASE_URL=postgres://$DB_USER:$DB_PASS@$DB_HOST:$DB_PORT/$DB_NAME - -# Apply the schema explicitly to this local database when ready: -# pnpm --dir backend migrate diff --git a/backend/src/auth/session-store.ts b/backend/src/auth/session-store.ts index 8f59d27..087a46e 100644 --- a/backend/src/auth/session-store.ts +++ b/backend/src/auth/session-store.ts @@ -10,8 +10,6 @@ export interface SessionRedis { del(key: string): Promise<unknown>; } -// OIDC stores have a callback contract, unlike the current connect-redis API. -// Expire the Redis data with the OIDC session itself. export function createSessionStore(client: SessionRedis): OidcStore { const key = (sid: string) => `bookit:oidc:${sid}`; return { diff --git a/backend/src/index.ts b/backend/src/index.ts index f59dc1e..c5054be 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -20,15 +20,13 @@ const requiredEnvironment = (name: string): string => { }; async function main() { - // Booking rule clock times belong to the division's local time zone. process.env.TZ ??= "Europe/Stockholm"; const app = express(); app.disable("x-powered-by"); if (process.env.TRUST_PROXY === "1") app.set("trust proxy", 1); const httpServer = createServer(app); const redis = createClient({ - // Redis 5 predates HELLO/RESP3. Keep the modern client on its supported - // RESP2 protocol until the separately planned Redis server upgrade. + // Redis 5 does not support RESP3. RESP: 2, maintNotifications: "disabled", socket: { diff --git a/backend/src/services/event.service.ts b/backend/src/services/event.service.ts index 4b1fc51..ee7f877 100644 --- a/backend/src/services/event.service.ts +++ b/backend/src/services/event.service.ts @@ -180,9 +180,7 @@ const toEvent = (event: Event) => ({ const isSerializationFailure = (error: unknown): boolean => { if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2034") return true; - // Prisma's PostgreSQL driver can surface commit failures directly instead of - // wrapping them as P2034. Its structured kind maps SQLSTATE 40001/40P01; - // matching the message alone could accidentally retry unrelated failures. + // Driver-adapter commit failures can bypass Prisma's P2034 wrapper. return ( error instanceof globalThis.Error && error.name === "DriverAdapterError" && @@ -193,9 +191,7 @@ const isSerializationFailure = (error: unknown): boolean => { ); }; -// Checking availability and writing must share a serializable transaction. -// PostgreSQL then rejects concurrent writes based on the same availability -// snapshot; retrying rechecks the winner's booking before writing again. +// Retry availability checks and writes together after serialization conflicts. export const withBookingTransaction = async ( prisma: PrismaClient, operation: (transaction: Prisma.TransactionClient) => Promise<Error | null>, @@ -210,8 +206,6 @@ export const withBookingTransaction = async ( }); } catch (error) { if (!isSerializationFailure(error)) throw error; - // Jitter prevents a burst of valid bookings from retrying in lockstep. - // The failed transaction has rolled back before we wait or try again. if (attempt + 1 < maxAttempts) await delay(20 * 2 ** attempt + Math.random() * 30); } } @@ -226,7 +220,6 @@ export const editEvent = async ( event: Event, user: User, ): Promise<Error | null> => { - // Sanity checks if (event.id == null) { return { sv: "Inget boknings id angivet", @@ -244,12 +237,7 @@ export const editEvent = async ( en: "You do not have permission to edit this event", }; } - // Group members may edit bookings without access to the author's phone. - // Keep both fields together: changing the author would grant the editor - // access to a phone number that belonged to someone else. - // After privacy cleanup removes the author, require a fresh phone number - // and make the authenticated editor the new contact. Never reclaim an old - // phone number whose owner is unknown. + // Never transfer access to a previous author's phone number to the editor. const updated = { ...event, phone: event.phone ?? (previous.booked_by ? previous.phone : ""), diff --git a/backend/src/services/rule.service.ts b/backend/src/services/rule.service.ts index 0349438..e6de1c5 100644 --- a/backend/src/services/rule.service.ts +++ b/backend/src/services/rule.service.ts @@ -55,7 +55,6 @@ export const toExplicitRules = (rules: rule[], from: Date, to: Date): ExplicitRu end.setHours(0, 0, 0, 0); while (current <= end) { if (dayApplies(current, rule.day_mask)) insertRule(explicitRules, current, rule); - // Calendar-day arithmetic also advances correctly across daylight-saving changes. current.setDate(current.getDate() + 1); } } @@ -131,8 +130,7 @@ const stockholmDate = new Intl.DateTimeFormat("sv-SE", { day: "2-digit", }); -// Rule dates are stored as UTC-midnight calendar dates, not instant cutoffs. -// Include the whole last effective day in the Swedish booking calendar. +// UTC-midnight rule dates include the entire last day in Stockholm time. export const ruleDateBounds = (from: Date, to: Date) => ({ end_date: { gte: new Date(`${stockholmDate.format(from)}T00:00:00.000Z`) }, start_date: { lte: new Date(`${stockholmDate.format(to)}T00:00:00.000Z`) }, diff --git a/backend/src/tests/booking-transaction.test.ts b/backend/src/tests/booking-transaction.test.ts index 8a15168..88a9b9e 100644 --- a/backend/src/tests/booking-transaction.test.ts +++ b/backend/src/tests/booking-transaction.test.ts @@ -13,8 +13,6 @@ const conflict = () => code: "P2034", clientVersion: "7.10.0", }); -// This is the DriverAdapterError shape emitted by @prisma/adapter-pg for -// PostgreSQL serialization_failure (40001) and deadlock_detected (40P01). const driverConflict = () => Object.assign( new Error("TransactionWriteConflict", { cause: { kind: "TransactionWriteConflict" } }), @@ -131,7 +129,6 @@ describe("Booking transaction recovery", () => { "requires a fresh valid phone for an anonymized booking, not %s", async (phone) => { vi.spyOn(prisma, "$transaction").mockImplementation(async (operation) => operation(prisma)); - // Even inconsistent legacy data must not expose an unowned phone number. vi.spyOn(prisma.event, "findUnique").mockResolvedValue({ ...anonymized, phone: "0701234567", diff --git a/backend/src/tests/graphql.test.ts b/backend/src/tests/graphql.test.ts index 8decf4a..7218b09 100644 --- a/backend/src/tests/graphql.test.ts +++ b/backend/src/tests/graphql.test.ts @@ -8,8 +8,7 @@ import { mergeTypeDefs } from "@graphql-tools/merge"; import { getResolvers } from "../resolvers"; import type { Context } from "../utils/commonTypes"; -// Requests in this suite must be rejected before any database access. There is -// deliberately no database listening here; the E2E suite tests actual persistence. +// An unreachable database ensures validation failures do not query it. const prisma = new PrismaClient({ adapter: new PrismaPg({ connectionString: "postgresql://test:test@127.0.0.1:1/test" }), }); diff --git a/backend/startup.sh b/backend/startup.sh index a893f85..56cc7d2 100644 --- a/backend/startup.sh +++ b/backend/startup.sh @@ -1,10 +1,8 @@ #!/bin/sh set -eu -# Preserve DB_* deployment configuration; DATABASE_URL can supply escaped credentials. if [ -z "${DATABASE_URL:-}" ]; then export DATABASE_URL="postgres://${DB_USER:?}:${DB_PASS:?}@${DB_HOST:?}:${DB_PORT:-5432}/${DB_NAME:?}" fi -# Apply database schema changes explicitly as a reviewed release step. exec node ./build/index.js diff --git a/codegen.ts b/codegen.ts index 0667a5e..aec6271 100644 --- a/codegen.ts +++ b/codegen.ts @@ -9,7 +9,6 @@ import { loadFilesSync } from "@graphql-tools/load-files"; import { mergeTypeDefs } from "@graphql-tools/merge"; import { type DocumentNode } from "graphql"; -// Local schemas need none of the CLI's remote loaders and deprecated fetch polyfills. async function generate() { const schema = mergeTypeDefs(loadFilesSync("backend/src/schemas/v1/*.gql")); const documents = loadFilesSync<DocumentNode>("frontend/src/**/*.graphql").map((document) => ({ diff --git a/db-scripts/cleanup.sh b/db-scripts/cleanup.sh index 0e2601b..225b333 100644 --- a/db-scripts/cleanup.sh +++ b/db-scripts/cleanup.sh @@ -1,5 +1,4 @@ #!/bin/sh -# Retain the daily privacy cleanup without the unsupported Jobber image. set -eu trap 'exit 0' INT TERM while :; do diff --git a/e2e/booking-helpers.ts b/e2e/booking-helpers.ts index 7fe86b5..cfb0df0 100644 --- a/e2e/booking-helpers.ts +++ b/e2e/booking-helpers.ts @@ -2,7 +2,6 @@ import { expect, type Page } from "@playwright/test"; import { dateSegments, fillSegments } from "./date-time-helpers"; export async function bookingDate(page: Page): Promise<string> { - // Match the browser's date, including CI's configured timezone. return page.evaluate(() => { const now = new Date(); diff --git a/e2e/booking-lifecycle.spec.ts b/e2e/booking-lifecycle.spec.ts index 3149f0b..d8334b6 100644 --- a/e2e/booking-lifecycle.spec.ts +++ b/e2e/booking-lifecycle.spec.ts @@ -55,8 +55,6 @@ test("a group member creates, edits and deletes a persisted booking", async ({ p }); test("simultaneous requests cannot reserve the same room and time twice", async ({ page }) => { - // These requests race over HTTP against the real PostgreSQL-backed service. - // Checking availability and inserting must form one atomic operation. const date = await bookingDate(page); const titles = ["E2E concurrent booking A", "E2E concurrent booking B"]; diff --git a/e2e/calendar.spec.ts b/e2e/calendar.spec.ts index ed61a0d..a4c8d55 100644 --- a/e2e/calendar.spec.ts +++ b/e2e/calendar.spec.ts @@ -36,8 +36,7 @@ test("editing immediately after a calendar drag preserves the moved times", asyn const event = page.getByRole("button").filter({ hasText: title }); - // Prime the detail cache, then move and edit before its 20-second freshness - // window expires. Saving another field must not restore the pre-drag times. + // Prime the detail cache before moving the booking. const readBooking = async () => { const { events } = await graphql<{ events: { title: string; start: string; end: string }[]; diff --git a/e2e/compose.ts b/e2e/compose.ts index 74deead..94a2aa7 100644 --- a/e2e/compose.ts +++ b/e2e/compose.ts @@ -9,8 +9,6 @@ import { applicationImages } from "./application-images"; const root = resolve(__dirname, ".."); -// BookIT intentionally retains its deployed database/server major versions. -// Gamma matches the working chalmers.it test/gamma-integration reference. export const images = { bookitPostgres: "postgres:12.22-alpine", bookitRedis: "redis:5.0.14-alpine", @@ -20,7 +18,6 @@ export const images = { "ghcr.io/cthit/gamma:2.5.1@sha256:5112c5673ee5c98b072c38afe4ed0de5f7f7d6ae85c41bbd318c92f0e3d5d9db", } as const; -// Synthetic identities are bootstrapped into the real Gamma application. export const users = { admin: { id: "88eec5c2-5ebb-4e13-9a76-fcc4dac9e74f", @@ -137,7 +134,7 @@ async function provisionClient(browser: Browser, gammaUrl: string, appUrl: strin throw new Error("Gamma did not return complete OAuth and CLIENT API credentials"); } - // BookIT admin means this client's authority, not Gamma's global admin flag. + // BookIT uses client-specific authority, not Gamma's global admin flag. await page.locator('[name="authority"]').fill("admin"); await page.getByRole("button", { name: "Add user authority", exact: true }).click(); await page.locator(".authority-user-row").selectOption(users.admin.id); @@ -179,7 +176,6 @@ export async function loginAs( } export async function compose(browser: Browser): Promise<Environment> { - // Validate before allocating any containers; CI must never fall back to dev servers. const appImages = applicationImages(); const network = await new Network().start(); const containers: StartedTestContainer[] = []; @@ -188,7 +184,6 @@ export async function compose(browser: Browser): Promise<Environment> { let stopped = false; const record = (name: string, chunk: Buffer | string) => { - // Gamma's development bootstrap prints synthetic credentials. Keep them out of CI artifacts. const text = String(chunk) .replace(/(password:)\S+/g, "$1[REDACTED]") .replace(/(and code:)\s*\S+/g, "$1 [REDACTED]"); @@ -291,8 +286,7 @@ export async function compose(browser: Browser): Promise<Environment> { ); const gammaPort = await unusedPort(); - // Chromium resolves *.localhost to loopback. Docker resolves the same name - // through Gamma's network alias, keeping the OIDC issuer identical on both sides. + // One issuer URL: browser loopback, Docker network alias. const gammaHost = appImages ? "gamma.localhost" : "localhost"; const gammaUrl = `http://${gammaHost}:${gammaPort}`; const gammaContainerPort = appImages ? gammaPort : 8081; diff --git a/e2e/screenshots.ts b/e2e/screenshots.ts index 50b1dd4..15bf270 100644 --- a/e2e/screenshots.ts +++ b/e2e/screenshots.ts @@ -14,7 +14,6 @@ async function main() { await mkdir(outputDirectory, { recursive: true }); const browser = await chromium.launch(); try { - // Match the supplied 3840 × 2234 Retina references at a 1920 × 1117 CSS viewport. const page = await browser.newPage({ baseURL: appUrl, viewport: { width: 1920, height: 1117 }, diff --git a/e2e/stress-helpers.ts b/e2e/stress-helpers.ts index 50b2cd1..768d1b7 100644 --- a/e2e/stress-helpers.ts +++ b/e2e/stress-helpers.ts @@ -21,7 +21,6 @@ export async function populateStress(page: Page, concurrency = 6) { const rules = data.rules.filter((rule) => !existingRules.has(`${rule.title}|${rule.start_date}`)); const timings: number[] = []; - // Resume partial local runs without deleting existing bookings or hiding errors. for (let offset = 0; offset < events.length; offset += concurrency) { await Promise.all( events.slice(offset, offset + concurrency).map(async (event) => { diff --git a/e2e/stress.spec.ts b/e2e/stress.spec.ts index 34e2ee8..f7b3990 100644 --- a/e2e/stress.spec.ts +++ b/e2e/stress.spec.ts @@ -31,7 +31,6 @@ test("a busy fortnight preserves bookings, diagonal stripes and rule pagination" await expect(stripedBooking).toBeVisible(); await expect(stripedBooking).toHaveCSS("background-image", /repeating-linear-gradient\(45deg/); - // Both the time and title must fit inside narrow striped cards. await page.setViewportSize({ width: 1174, height: 1039 }); const labelsFit = await stripedBooking.evaluate((card) => { const bounds = card.getBoundingClientRect(); @@ -54,7 +53,6 @@ test("a busy fortnight preserves bookings, diagonal stripes and rule pagination" await expect(details).toContainText("CTC"); await page.keyboard.press("Escape"); - // Arrow keys must change the view, not only the visual tab selection. await page.getByRole("tab", { name: "Week view", exact: true }).focus(); await page.keyboard.press("ArrowLeft"); await expect(page.getByRole("tab", { name: "List view", exact: true })).toHaveAttribute( diff --git a/frontend/.dockerignore b/frontend/.dockerignore deleted file mode 100644 index dd87e2d..0000000 --- a/frontend/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -build diff --git a/frontend/.env b/frontend/.env deleted file mode 100644 index 4b2b446..0000000 --- a/frontend/.env +++ /dev/null @@ -1,2 +0,0 @@ -BROWSER=none -PORT=3001 diff --git a/frontend/.gitignore b/frontend/.gitignore deleted file mode 100644 index 4d29575..0000000 --- a/frontend/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# production -/build - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* diff --git a/frontend/Dockerfile b/frontend/Dockerfile index d6d5320..7b61725 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,4 +1,3 @@ -# Build from the repository root: docker build -f frontend/Dockerfile . FROM node:24.19.0-bookworm-slim AS build ENV CI=true COREPACK_HOME=/opt/corepack RUN corepack enable && corepack prepare pnpm@12.3.4 --activate diff --git a/frontend/dev_setup.sh b/frontend/dev_setup.sh deleted file mode 100755 index 0f5bd32..0000000 --- a/frontend/dev_setup.sh +++ /dev/null @@ -1,2 +0,0 @@ -# From the repository root: pnpm install && pnpm --dir frontend dev -# Vite+ uses modern Node directly; no legacy OpenSSL provider is needed. diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json deleted file mode 100644 index 812f0d5..0000000 --- a/frontend/public/manifest.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "short_name": "BookIT", - "name": "Online booking service for the Software Engineering Student Division (IT)", - "icons": [ - { - "src": "favicon.ico", - "sizes": "16x16", - "type": "image/x-icon" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" -} diff --git a/frontend/src/components/date-time-fields.tsx b/frontend/src/components/date-time-fields.tsx index abf0578..4f7dfd5 100644 --- a/frontend/src/components/date-time-fields.tsx +++ b/frontend/src/components/date-time-fields.tsx @@ -100,8 +100,6 @@ export function DatePicker({ sideOffset={8} collisionPadding={16} onOpenAutoFocus={(event) => { - // Let Radix establish its focus scope before focusing the calendar. - // This also works when the picker is nested inside a rule dialog. event.preventDefault(); calendarRef.current ?.querySelector<HTMLElement>('[role="button"][tabindex="0"]') diff --git a/frontend/src/components/ui/alert.tsx b/frontend/src/components/ui/alert.tsx deleted file mode 100644 index f8feeb8..0000000 --- a/frontend/src/components/ui/alert.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import * as React from "react"; -import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "cn"; - -const alertVariants = cva( - "relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", - { - variants: { - variant: { - default: "bg-card text-card-foreground", - destructive: - "bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 [&>svg]:text-current", - }, - }, - defaultVariants: { - variant: "default", - }, - }, -); - -function Alert({ - className, - variant, - ...props -}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) { - return ( - <div - data-slot="alert" - role="alert" - className={cn(alertVariants({ variant }), className)} - {...props} - /> - ); -} - -function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { - return ( - <div - data-slot="alert-title" - className={cn("col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight", className)} - {...props} - /> - ); -} - -function AlertDescription({ className, ...props }: React.ComponentProps<"div">) { - return ( - <div - data-slot="alert-description" - className={cn( - "col-start-2 grid justify-items-start gap-1 text-sm text-muted-foreground [&_p]:leading-relaxed", - className, - )} - {...props} - /> - ); -} - -export { Alert, AlertTitle, AlertDescription }; diff --git a/frontend/src/components/ui/event-calendar-views.tsx b/frontend/src/components/ui/event-calendar-views.tsx index 4eae58a..95cae43 100644 --- a/frontend/src/components/ui/event-calendar-views.tsx +++ b/frontend/src/components/ui/event-calendar-views.tsx @@ -17,9 +17,6 @@ const tallDayCellBottomClass = "min-h-4"; const getShortDayCellBottomClass = (info: DayCellInfo) => cn(!info.isNarrow && "min-h-px"); const dayRowCommonClasses: CalendarOptions = { - /* Day Row > List-Item Event - ----------------------------------------------------------------------------------------------- */ - listItemEventClass: (info) => cn("mb-1 p-0.5 rounded-sm", info.isNarrow ? "mx-px" : "mx-1"), listItemEventBeforeClass: (info) => cn("border-4 rounded-full border-(--fc-event-color)", info.isNarrow ? "ms-0.5" : "ms-1"), @@ -30,15 +27,9 @@ const dayRowCommonClasses: CalendarOptions = { listItemEventTitleClass: (info) => cn(info.isNarrow ? "px-0.5" : "px-1", "font-bold whitespace-nowrap overflow-hidden shrink-100"), - /* Day Row > Row Event - ----------------------------------------------------------------------------------------------- */ - rowEventClass: (info) => cn(info.isStart && "ms-px", info.isEnd && "me-px"), rowEventInnerClass: (info) => cn(info.isNarrow ? "py-px" : "py-0.5"), - /* Day Row > More-Link - ----------------------------------------------------------------------------------------------- */ - rowMoreLinkClass: (info) => cn( "mb-px border rounded-sm hover:bg-foreground/5", @@ -49,14 +40,11 @@ const dayRowCommonClasses: CalendarOptions = { }; export type EventCalendarViewProps = CalendarOptions & - Required<Pick<CalendarOptions, "popoverCloseContent">>; // ensure callers define icons + Required<Pick<CalendarOptions, "popoverCloseContent">>; export function EventCalendarViews({ views: userViews, ...restOptions }: EventCalendarViewProps) { return ( <FullCalendar - /* Abstract Event - ----------------------------------------------------------------------------------------- */ - eventShortHeight={50} eventColor="var(--primary)" eventContrastColor="var(--primary-foreground)" @@ -69,9 +57,6 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa ) } - /* Background Event - ----------------------------------------------------------------------------------------- */ - backgroundEventColor="var(--chart-3)" backgroundEventClass="not-print:bg-[color-mix(in_oklab,var(--fc-event-color)_10%,transparent)] print:border-1 print:border-(--fc-event-color)" backgroundEventTitleClass={(info) => @@ -81,18 +66,12 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa ) } - /* List-Item Event - ----------------------------------------------------------------------------------------- */ - listItemEventClass={(info) => cn("items-center", info.isSelected ? "bg-foreground/5" : "hover:bg-foreground/5") } listItemEventBeforeClass="rounded-full border-(--fc-event-color)" listItemEventInnerClass="flex flex-row items-center" - /* Block Event - ----------------------------------------------------------------------------------------- */ - blockEventClass={(info) => cn( "group relative border-transparent print:border-(--fc-event-color) bg-(--fc-event-color) hover:bg-[color-mix(in_oklab,var(--fc-event-color)_92%,var(--fc-event-contrast-color))] print:bg-white", @@ -105,9 +84,6 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa blockEventTimeClass="min-w-0 max-w-full truncate" blockEventTitleClass="min-w-0 max-w-full whitespace-normal wrap-anywhere overflow-hidden line-clamp-2 leading-snug" - /* Row Event - ----------------------------------------------------------------------------------------- */ - rowEventClass={(info) => cn( "mb-px border-y", @@ -122,15 +98,12 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa ? "absolute -start-2 w-2 -top-px -bottom-px" : "" } - rowEventBeforeContent={ - (info) => - !info.isStart && !info.isNarrow ? ( - filledRightTriangle( - "size-full rotate-180 [[dir=rtl]_&]:rotate-0 text-(--fc-event-color)", - ) - ) : ( - <></> - ) // HACK for React vdom + rowEventBeforeContent={(info) => + !info.isStart && !info.isNarrow ? ( + filledRightTriangle("size-full rotate-180 [[dir=rtl]_&]:rotate-0 text-(--fc-event-color)") + ) : ( + <></> + ) } rowEventAfterClass={(info) => info.isEndResizable @@ -139,13 +112,12 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa ? "absolute -end-2 w-2 -top-px -bottom-px" : "" } - rowEventAfterContent={ - (info) => - !info.isEnd && !info.isNarrow ? ( - filledRightTriangle("size-full [[dir=rtl]_&]:rotate-180 text-(--fc-event-color)") - ) : ( - <></> - ) // HACK for React vdom + rowEventAfterContent={(info) => + !info.isEnd && !info.isNarrow ? ( + filledRightTriangle("size-full [[dir=rtl]_&]:rotate-180 text-(--fc-event-color)") + ) : ( + <></> + ) } rowEventInnerClass={(info) => cn("flex flex-row items-center", info.isNarrow ? xxsTextClass : "text-xs") @@ -153,9 +125,6 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa rowEventTimeClass={(info) => cn("font-bold shrink-1", info.isNarrow ? "ps-0.5" : "ps-1")} rowEventTitleClass={(info) => cn("shrink-100", info.isNarrow ? "px-0.5" : "px-1")} - /* Column Event - ----------------------------------------------------------------------------------------- */ - columnEventTitleSticky={false} columnEventClass={(info) => cn( @@ -196,17 +165,11 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa cn("order-1 shrink-1", !info.isShort && (info.isNarrow ? "py-0.5" : "py-1")) } - /* More-Link - ----------------------------------------------------------------------------------------- */ - moreLinkClass="focus-visible:outline-3 outline-ring/50" moreLinkInnerClass="whitespace-nowrap overflow-hidden" columnMoreLinkClass="mb-px border border-transparent print:border-black rounded-sm bg-[color-mix(in_oklab,var(--foreground)_10%,var(--background))] hover:bg-[color-mix(in_oklab,var(--foreground)_13%,var(--background))] print:bg-white ring ring-background" columnMoreLinkInnerClass={(info) => (info.isNarrow ? `p-0.5 ${xxsTextClass}` : "p-1 text-xs")} - /* Day Header - ----------------------------------------------------------------------------------------- */ - dayHeaderAlign="center" dayHeaderClass={(info) => cn( @@ -238,9 +201,6 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa </> )} - /* Day Cell - ----------------------------------------------------------------------------------------- */ - dayCellClass={(info) => cn("border", info.isMajor && "border-foreground/20", info.isDisabled && "bg-foreground/3") } @@ -270,16 +230,10 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa } dayCellInnerClass={(info) => cn(info.inPopover && "p-2")} - /* Popover - ----------------------------------------------------------------------------------------- */ - popoverFormat={{ day: "numeric", weekday: "short" }} popoverClass="border rounded-md overflow-hidden shadow-lg m-1 bg-popover text-popover-foreground min-w-60" popoverCloseClass="group absolute top-2 end-2 size-8 rounded-full items-center justify-center hover:bg-foreground/5 focus-visible:outline-3 outline-ring/50" - /* Lane - ----------------------------------------------------------------------------------------- */ - dayLaneClass={(info) => cn("border", info.isMajor && "border-foreground/20", info.isDisabled && "bg-foreground/3") } @@ -288,9 +242,6 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa } slotLaneClass={(info) => cn("border", info.isMinor && "border-dotted")} - /* List Day - ----------------------------------------------------------------------------------------- */ - listDayFormat={{ day: "numeric" }} listDayAltFormat={{ month: "short", weekday: "short", forceCommas: true }} listDayClass={(info) => cn(!info.isLast && "border-b", "flex flex-row items-start")} @@ -314,9 +265,6 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa } listDayBodyClass="grow min-w-0 py-3 pe-3 sm:pe-5 gap-2" - /* Single Month (in Multi-Month) - ----------------------------------------------------------------------------------------- */ - singleMonthClass={(info) => cn( info.multiMonthColumns > 1 && "m-4", @@ -330,18 +278,12 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa cn("px-3 py-1 rounded-full text-base font-bold", info.hasNavLink && "hover:bg-foreground/5") } - /* Misc Table - ----------------------------------------------------------------------------------------- */ - tableHeaderClass="bg-background" fillerClass={(info) => cn("opacity-50 border", info.inTableHeader && "border-transparent")} dayNarrowWidth={100} dayHeaderRowClass="border" dayRowClass="border" - /* Misc Content - ----------------------------------------------------------------------------------------- */ - navLinkClass="focus-visible:outline-3 outline-ring/50" inlineWeekNumberClass={(info) => cn( @@ -357,9 +299,6 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa nowIndicatorLineClass="-m-px border-1 border-destructive" nowIndicatorDotClass="-m-[6px] border-6 border-destructive size-0 rounded-full ring-2 ring-background" - /* View-Specific Options - ----------------------------------------------------------------------------------------- */ - views={{ ...userViews, dayGrid: { @@ -380,9 +319,6 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa ...dayRowCommonClasses, dayCellBottomClass: tallDayCellBottomClass, - /* TimeGrid > Week Number Header - ------------------------------------------------------------------------------------- */ - weekNumberHeaderClass: "items-center justify-end", weekNumberHeaderInnerClass: (info) => cn( @@ -392,17 +328,11 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa info.hasNavLink && "hover:bg-foreground/20 focus-visible:outline-3 outline-ring/50", ), - /* TimeGrid > All-Day Header - ------------------------------------------------------------------------------------- */ - allDayHeaderClass: "items-center justify-end", allDayHeaderInnerClass: (info) => cn("m-2 text-end", info.isNarrow ? xxsTextClass : "text-sm"), allDayDividerClass: "border-b", - /* TimeGrid > Slot Header - ------------------------------------------------------------------------------------- */ - slotHeaderClass: (info) => cn("w-2 self-end justify-end", "border", info.isMinor && "border-dotted"), slotHeaderInnerClass: (info) => @@ -420,9 +350,6 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa ...userViews?.timeGrid, }, list: { - /* List-View > List-Item Event - ------------------------------------------------------------------------------------- */ - listItemEventClass: "group px-3 py-3 rounded-md gap-2", listItemEventBeforeClass: "mx-2 border-5 rounded-full border-(--fc-event-color)", listItemEventInnerClass: @@ -434,9 +361,6 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa info.event.url && "group-hover:underline", ), - /* No-Events Screen - ------------------------------------------------------------------------------------- */ - noEventsClass: "grow flex flex-col items-center justify-center", noEventsInnerClass: "py-15", @@ -449,9 +373,6 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa ); } -/* SVGs -------------------------------------------------------------------------------------------------- */ - function filledRightTriangle(className?: string) { return ( <svg diff --git a/frontend/src/components/ui/select.tsx b/frontend/src/components/ui/select.tsx deleted file mode 100644 index 0218de4..0000000 --- a/frontend/src/components/ui/select.tsx +++ /dev/null @@ -1,174 +0,0 @@ -"use client"; - -import * as React from "react"; -import { cn } from "cn"; -import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"; -import { Select as SelectPrimitive } from "radix-ui"; - -function Select({ ...props }: React.ComponentProps<typeof SelectPrimitive.Root>) { - return <SelectPrimitive.Root data-slot="select" {...props} />; -} - -function SelectGroup({ ...props }: React.ComponentProps<typeof SelectPrimitive.Group>) { - return <SelectPrimitive.Group data-slot="select-group" {...props} />; -} - -function SelectValue({ ...props }: React.ComponentProps<typeof SelectPrimitive.Value>) { - return <SelectPrimitive.Value data-slot="select-value" {...props} />; -} - -function SelectTrigger({ - className, - size = "default", - children, - ...props -}: React.ComponentProps<typeof SelectPrimitive.Trigger> & { - size?: "sm" | "default"; -}) { - return ( - <SelectPrimitive.Trigger - data-slot="select-trigger" - data-size={size} - className={cn( - "flex w-fit items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground", - className, - )} - {...props} - > - {children} - <SelectPrimitive.Icon asChild> - <ChevronDownIcon className="size-4 opacity-50" /> - </SelectPrimitive.Icon> - </SelectPrimitive.Trigger> - ); -} - -function SelectContent({ - className, - children, - position = "item-aligned", - align = "center", - ...props -}: React.ComponentProps<typeof SelectPrimitive.Content>) { - return ( - <SelectPrimitive.Portal> - <SelectPrimitive.Content - data-slot="select-content" - className={cn( - "relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95", - position === "popper" && - "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", - className, - )} - position={position} - align={align} - {...props} - > - <SelectScrollUpButton /> - <SelectPrimitive.Viewport - className={cn( - "p-1", - position === "popper" && - "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1", - )} - > - {children} - </SelectPrimitive.Viewport> - <SelectScrollDownButton /> - </SelectPrimitive.Content> - </SelectPrimitive.Portal> - ); -} - -function SelectLabel({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Label>) { - return ( - <SelectPrimitive.Label - data-slot="select-label" - className={cn("px-2 py-1.5 text-xs text-muted-foreground", className)} - {...props} - /> - ); -} - -function SelectItem({ - className, - children, - ...props -}: React.ComponentProps<typeof SelectPrimitive.Item>) { - return ( - <SelectPrimitive.Item - data-slot="select-item" - className={cn( - "relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2", - className, - )} - {...props} - > - <span - data-slot="select-item-indicator" - className="absolute right-2 flex size-3.5 items-center justify-center" - > - <SelectPrimitive.ItemIndicator> - <CheckIcon className="size-4" /> - </SelectPrimitive.ItemIndicator> - </span> - <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText> - </SelectPrimitive.Item> - ); -} - -function SelectSeparator({ - className, - ...props -}: React.ComponentProps<typeof SelectPrimitive.Separator>) { - return ( - <SelectPrimitive.Separator - data-slot="select-separator" - className={cn("pointer-events-none -mx-1 my-1 h-px bg-border", className)} - {...props} - /> - ); -} - -function SelectScrollUpButton({ - className, - ...props -}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) { - return ( - <SelectPrimitive.ScrollUpButton - data-slot="select-scroll-up-button" - className={cn("flex cursor-default items-center justify-center py-1", className)} - {...props} - > - <ChevronUpIcon className="size-4" /> - </SelectPrimitive.ScrollUpButton> - ); -} - -function SelectScrollDownButton({ - className, - ...props -}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) { - return ( - <SelectPrimitive.ScrollDownButton - data-slot="select-scroll-down-button" - className={cn("flex cursor-default items-center justify-center py-1", className)} - {...props} - > - <ChevronDownIcon className="size-4" /> - </SelectPrimitive.ScrollDownButton> - ); -} - -export { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectLabel, - SelectScrollDownButton, - SelectScrollUpButton, - SelectSeparator, - SelectTrigger, - SelectValue, -}; diff --git a/frontend/src/components/ui/separator.tsx b/frontend/src/components/ui/separator.tsx deleted file mode 100644 index c51c144..0000000 --- a/frontend/src/components/ui/separator.tsx +++ /dev/null @@ -1,27 +0,0 @@ -"use client"; - -import * as React from "react"; -import { cn } from "cn"; -import { Separator as SeparatorPrimitive } from "radix-ui"; - -function Separator({ - className, - orientation = "horizontal", - decorative = true, - ...props -}: React.ComponentProps<typeof SeparatorPrimitive.Root>) { - return ( - <SeparatorPrimitive.Root - data-slot="separator" - decorative={decorative} - orientation={orientation} - className={cn( - "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px", - className, - )} - {...props} - /> - ); -} - -export { Separator }; diff --git a/frontend/src/components/ui/skeleton.tsx b/frontend/src/components/ui/skeleton.tsx deleted file mode 100644 index ce70ac2..0000000 --- a/frontend/src/components/ui/skeleton.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { cn } from "cn"; - -function Skeleton({ className, ...props }: React.ComponentProps<"div">) { - return ( - <div - data-slot="skeleton" - className={cn("animate-pulse rounded-md bg-accent", className)} - {...props} - /> - ); -} - -export { Skeleton }; diff --git a/frontend/src/lib/booking-colors.ts b/frontend/src/lib/booking-colors.ts index c0af27a..20d7317 100644 --- a/frontend/src/lib/booking-colors.ts +++ b/frontend/src/lib/booking-colors.ts @@ -10,8 +10,6 @@ export function bookingRoomClass(selected: readonly (Room | null)[]) { return `booking-rooms-${mask}${multiple ? " booking-multi-room" : ""}`; } -// There are only 15 nonempty combinations of the four rooms. Declarative classes -// keep colors correct after edits, filtering and view changes, not just on mount. export const bookingRoomStyles = Array.from({ length: (1 << rooms.length) - 1 }, (_, index) => { const mask = index + 1; const colors = rooms.filter((_, bit) => mask & (1 << bit)).map((room) => room.color); diff --git a/frontend/src/lib/calendar-blocks.ts b/frontend/src/lib/calendar-blocks.ts index b32def2..930c3ba 100644 --- a/frontend/src/lib/calendar-blocks.ts +++ b/frontend/src/lib/calendar-blocks.ts @@ -14,8 +14,7 @@ interface CalendarBlock { description: string; } -// The API resolves rules separately for each room. Combine only their display, -// after room filtering, so simultaneous restrictions cannot paint overlapping labels. +// Merge per-room restrictions for display without changing booking validation. export function calendarBlocks(slots: readonly BlockedSlot[]): CalendarBlock[] { const intervals = slots.map((slot) => ({ ...slot, diff --git a/frontend/src/pages/rules.tsx b/frontend/src/pages/rules.tsx index 0e5ef05..0e88405 100644 --- a/frontend/src/pages/rules.tsx +++ b/frontend/src/pages/rules.tsx @@ -126,7 +126,6 @@ export function RulesPage() { room: selected, start_date: formText(form, "start_date"), end_date: formText(form, "end_date"), - // Accessible time fields serialize ISO times; the API uses minute precision. start_time: formText(form, "start_time").slice(0, 5), end_time: formText(form, "end_time").slice(0, 5), }; diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx index 4d35187..0c18613 100644 --- a/frontend/src/router.tsx +++ b/frontend/src/router.tsx @@ -136,7 +136,6 @@ const rulesRoute = createRoute({ path: "/rules", component: lazyRouteComponent(() => import("@/pages/rules"), "RulesPage"), }); -// Preserve bookmarks from the previous router. const legacyEditRoute = createRoute({ getParentRoute: () => rootRoute, path: "/edit-event", diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index bc5c8fc..8b96b72 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -9,8 +9,7 @@ overrides: "prisma>mysql2": 3.24.3 "archiver-utils>glob": 13.0.6 -# Vite+ exposes Vite 8.2.2 under its distribution version 0.3.0. -# These tested Vite-8 consumers otherwise warn about the alias version number. +# Vite+ reports its distribution version (0.3.0), not its Vite version (8.2.2). peerDependencyRules: allowedVersions: "vitest>vite": "0.3.0" diff --git a/prod.docker-compose.yml b/prod.docker-compose.yml index a8bb5bc..edaad32 100644 --- a/prod.docker-compose.yml +++ b/prod.docker-compose.yml @@ -1,4 +1,3 @@ -# Uses an existing Gamma installation. See README.md before deployment. services: backend: build: diff --git a/queries.md b/queries.md deleted file mode 100644 index 9bf5448..0000000 --- a/queries.md +++ /dev/null @@ -1,43 +0,0 @@ -## SQL queries - -### Insert event - -```SQL -INSERT INTO event (title, description, start, end_date, room) VALUES ( - 'Fun Event', - 'A verry good event', - '2021-08-10T21:00' , - '2021-08-10T22:00', - 'BIG_HUB'); -``` - -## GraphQL Queries - -### Query all - -```GraphQL -{ - events { - id - title - } -} -``` - -### Query specific - -```GraphQL -query GetEvent($id: String!) { - event(id: $id) { - id - } -} -``` - -Variables - -```json -{ - "id": "1068380c-f9f7-11eb-9702-0242ac1a0002" -} -``` From d5f8d4feb05db16fae90e69dea896363b762edae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 11:47:13 +0200 Subject: [PATCH 12/41] Remove development previews and one-off audit tooling --- README.md | 21 ++------- e2e/README.md | 38 ++--------------- e2e/accessibility.spec.ts | 80 ---------------------------------- e2e/compose.ts | 4 +- e2e/dev.ts | 48 --------------------- e2e/screenshots.ts | 52 ---------------------- e2e/stress-data.ts | 66 ---------------------------- e2e/stress-helpers.ts | 53 ----------------------- e2e/stress.spec.ts | 90 --------------------------------------- e2e/stress.ts | 28 ------------ package.json | 3 +- 11 files changed, 10 insertions(+), 473 deletions(-) delete mode 100644 e2e/accessibility.spec.ts delete mode 100644 e2e/dev.ts delete mode 100644 e2e/screenshots.ts delete mode 100644 e2e/stress-data.ts delete mode 100644 e2e/stress-helpers.ts delete mode 100644 e2e/stress.spec.ts delete mode 100644 e2e/stress.ts diff --git a/README.md b/README.md index 59a867f..fba80e2 100644 --- a/README.md +++ b/README.md @@ -30,18 +30,7 @@ pnpm --dir backend exec prisma generate Install scripts are explicitly approved in `pnpm-workspace.yaml`. Review new blocked scripts individually; do not globally enable dependency build scripts. -The fastest isolated local environment includes real Gamma and test-only identities: - -```sh -pnpm exec playwright install chromium -pnpm e2e:dev -``` - -Docker must be running with sufficient disk space. Open the printed BookIT URL. -Ctrl+C cleans up only that environment's processes, network and containers. -See [e2e/README.md](e2e/README.md) for roles and the service-version matrix. - -For an existing local Gamma installation: +With a local Gamma installation: 1. Start local backing services with `docker compose up -d db redis`. 2. Copy `backend/.env.example` to `backend/.env` and replace its placeholders. @@ -81,17 +70,15 @@ Both generated outputs are committed; CI rejects stale output. ## Browser verification -Complete a manual browser smoke pass before running the automated flows: +With Docker running: ```sh -pnpm e2e:dev -# Open the printed URL and check booking, editing/deletion, rules, language/mobile. -# Stop the manual environment, then: +pnpm exec playwright install chromium pnpm test:e2e ``` Playwright specs cover booking persistence, calendar filters and navigation, -administrator rules, authorization, accessibility, and busy calendars. The extended +administrator rules, and authorization. The extended test fixture authenticates a fresh browser context by default, with configurable admin/member/outsider identities. It uses real Gamma rather than mocked login. One `e2e/compose.ts` owns all Testcontainers orchestration and separate databases. diff --git a/e2e/README.md b/e2e/README.md index 3b5c089..1c9d304 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -13,8 +13,8 @@ to keep the suite's resource usage predictable. ## Development servers or published images -Local runs default to development servers (`E2E_MODE=dev`), including -`pnpm e2e:dev`. CI requires `E2E_MODE=images` and refuses to fall back to +Local runs default to development servers (`E2E_MODE=dev`). +CI requires `E2E_MODE=images` and refuses to fall back to development servers if either image is missing. To run the same suite locally against specific published candidates: @@ -31,8 +31,7 @@ pnpm test:e2e Replace each placeholder with its 64-character digest from the CI candidate job outputs/logs. Tags such as `latest` are rejected. Docker/Testcontainers use the host's Docker credential store; credentials are not forwarded into containers. -Public images do not require a login. Use the same variables with `pnpm e2e:dev` -to keep a published-image environment open for manual inspection. +Public images do not require a login. Image mode runs the exact backend digest once with `prisma db push` against the empty, isolated BookIT database, and checks its exit status before starting the @@ -87,10 +86,6 @@ consistency, language persistence, mobile layout, rule validation/lifecycle, and UI/API permissions have separate results. Each test gets its own login and data reset; the services stay shared for the worker. -Accessibility regressions cover keyboard date/time editing, leap-day selection, -focus restoration (including calendars inside dialogs), required-date validation, -and the mobile date picker layout. - Prefer `getByRole` with an accessible name and scope it to the relevant dialog or navigation when needed. Date/time fields use React Aria's named groups and editable spinbutton segments. `date-time-helpers.ts` enters values through the @@ -106,33 +101,6 @@ pnpm exec vp fmt e2e playwright.config.ts Use `pnpm format` for the whole repository. `pnpm check` checks E2E formatting and lint in CI as well. -For the manual smoke test, run `pnpm e2e:dev` and open the printed BookIT URL. -The command prints synthetic login credentials and keeps the environment alive. -Ctrl+C stops application processes and removes the test containers and network. - -To populate that local preview with 252 bookings and 24 rules, run: - -```sh -pnpm exec tsx e2e/stress.ts <local-app-url> <local-gamma-url> -``` - -The script uses synthetic admin credentials, accepts only localhost URLs, and -resumes without duplicating matching fixtures. It submits six writes concurrently -and reports their p95 response time. The CI stress spec uses the same dataset to -check persistence, multi-room diagonal stripes, calendar views, rule pagination, -and mobile overflow. This is a bounded local stress check, not a production -capacity benchmark. The normal concurrency test also verifies that conflicting -bookings cannot double-book a room. - -For screenshots matching the 3840 × 2234 reference images: - -```sh -pnpm exec tsx e2e/screenshots.ts <local-app-url> <local-gamma-url> <output-directory> -``` - -This captures week, month, list and rules from the populated preview at a -1920 × 1117 CSS viewport and 2× pixel density. - Tests import the extended `test` and `expect` from `./fixtures`. The default page logs in through Gamma as `member` before the test starts. A spec can use `test.use({ role: "admin" })` or `test.use({ role: "outsider" })`; `loginAs` lets a diff --git a/e2e/accessibility.spec.ts b/e2e/accessibility.spec.ts deleted file mode 100644 index 187c8e9..0000000 --- a/e2e/accessibility.spec.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { test, expect } from "./fixtures"; -import { bookingDate, fillBooking, graphql } from "./booking-helpers"; -import { dateSegments, expectSegments, fillSegments } from "./date-time-helpers"; - -test("booking dates support keyboard editing and calendar selection without losing the time", async ({ - page, -}) => { - await page.goto("/new-event"); - - const begins = page.getByRole("group", { name: "Begins at", exact: true }); - await fillSegments(begins, { year: 2028, month: 2, day: 28, hour: 12, minute: 30 }); - - const minute = begins.getByRole("spinbutton", { name: "minute, Begins at", exact: true }); - await minute.press("ArrowUp"); - await expectSegments(begins, { minute: 31 }); - await minute.press("ArrowDown"); - await minute.press("Tab"); - - const trigger = page.getByRole("button", { name: "Choose date for Begins at", exact: true }); - await expect(trigger).toBeFocused(); - await trigger.press("Enter"); - - const calendar = page.getByRole("dialog", { name: "Begins at calendar", exact: true }); - const selectedDay = calendar.getByRole("button", { name: /28 February 2028 selected/ }); - await expect(selectedDay).toBeFocused(); - await selectedDay.press("ArrowRight"); - - const leapDay = calendar.getByRole("button", { name: "Tuesday, 29 February 2028", exact: true }); - await expect(leapDay).toBeFocused(); - await leapDay.press("Enter"); - - await expect(calendar).not.toBeVisible(); - await expect(trigger).toBeFocused(); - await expectSegments(begins, { year: 2028, month: 2, day: 29, hour: 12, minute: 30 }); - - await trigger.press("Enter"); - await page.keyboard.press("Escape"); - await expect(calendar).not.toBeVisible(); - await expect(trigger).toBeFocused(); - - await page.setViewportSize({ width: 390, height: 844 }); - await trigger.click(); - await expect(calendar).toBeVisible(); - const bounds = await calendar.boundingBox(); - if (!bounds) throw new Error("The date picker has no visible bounds"); - expect(bounds.x).toBeGreaterThanOrEqual(0); - expect(bounds.x + bounds.width).toBeLessThanOrEqual(390); - expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(390); -}); - -test("an empty required date is described as invalid and prevents booking submission", async ({ - page, -}) => { - await page.goto("/new-event"); - await fillBooking(page, "E2E incomplete date"); - - const begins = page.getByRole("group", { name: "Begins at", exact: true }); - for (const segment of await begins.getByRole("spinbutton").all()) { - await segment.press("Home"); - await segment.press("Delete"); - } - const day = begins.getByRole("spinbutton", { name: "day, Begins at", exact: true }); - await page.getByRole("button", { name: "Save booking", exact: true }).click(); - - await expect(page).toHaveURL(/\/new-event$/); - await expect(day).toHaveAttribute("aria-invalid", "true"); - await expect(day).toBeFocused(); - await expect(page.getByText("Please fill out this field.", { exact: true })).toBeVisible(); - await expect(day).toHaveAccessibleDescription(/Please fill out this field\./); - - const { events } = await graphql<{ events: { title: string }[] }>(page, "{ events { title } }"); - expect(events).toHaveLength(0); - - await fillSegments(begins, { ...dateSegments(await bookingDate(page)), hour: 12, minute: 0 }); - await page.getByRole("button", { name: "Save booking", exact: true }).click(); - await expect(page).toHaveURL(/\/$/); - - const saved = await graphql<{ events: { title: string }[] }>(page, "{ events { title } }"); - expect(saved.events).toEqual([{ title: "E2E incomplete date" }]); -}); diff --git a/e2e/compose.ts b/e2e/compose.ts index 94a2aa7..47de860 100644 --- a/e2e/compose.ts +++ b/e2e/compose.ts @@ -18,7 +18,7 @@ export const images = { "ghcr.io/cthit/gamma:2.5.1@sha256:5112c5673ee5c98b072c38afe4ed0de5f7f7d6ae85c41bbd318c92f0e3d5d9db", } as const; -export const users = { +const users = { admin: { id: "88eec5c2-5ebb-4e13-9a76-fcc4dac9e74f", cid: "bookadmin", @@ -40,7 +40,7 @@ export const users = { } as const; export type UserRole = keyof typeof users; -export const testPassword = "password1337"; +const testPassword = "password1337"; const groupId = "aed27030-ad90-4526-855c-1e909b1dcecb"; const postId = "7bb1db15-730d-4864-bfc3-99abe7c0ccf8"; diff --git a/e2e/dev.ts b/e2e/dev.ts deleted file mode 100644 index f89c3de..0000000 --- a/e2e/dev.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { chromium } from "@playwright/test"; -import { compose, testPassword, users } from "./compose"; - -async function main() { - const browser = await chromium.launch(); - let stopEnvironment: (() => Promise<void>) | undefined; - let stopping = false; - - async function stop() { - if (stopping) return; - stopping = true; - - try { - await stopEnvironment?.(); - } finally { - await browser.close(); - } - } - - process.once("SIGINT", () => { - void stop(); - }); - process.once("SIGTERM", () => { - void stop(); - }); - - try { - const environment = await compose(browser); - stopEnvironment = () => environment.stop(); - - console.log(`BookIT smoke environment: ${environment.appUrl}`); - console.log(`Gamma: ${environment.gammaUrl}`); - console.log( - `Synthetic users: ${Object.values(users) - .map((user) => user.cid) - .join(", ")}; password: ${testPassword}`, - ); - console.log("Press Ctrl+C to stop and remove this isolated environment."); - } catch (error) { - await stop(); - throw error; - } -} - -void main().catch((error: unknown) => { - console.error(error); - process.exitCode = 1; -}); diff --git a/e2e/screenshots.ts b/e2e/screenshots.ts deleted file mode 100644 index 15bf270..0000000 --- a/e2e/screenshots.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { chromium, expect } from "@playwright/test"; -import { mkdir } from "node:fs/promises"; -import { resolve } from "node:path"; -import { loginAs } from "./compose"; - -async function main() { - const [appUrl, gammaUrl, outputDirectory] = process.argv.slice(2); - if (!appUrl || !gammaUrl || !outputDirectory) - throw new Error("Provide local app URL, Gamma URL and output directory"); - for (const url of [appUrl, gammaUrl]) { - if (!["localhost", "127.0.0.1", "gamma.localhost"].includes(new URL(url).hostname)) - throw new Error("Only localhost previews are supported"); - } - await mkdir(outputDirectory, { recursive: true }); - const browser = await chromium.launch(); - try { - const page = await browser.newPage({ - baseURL: appUrl, - viewport: { width: 1920, height: 1117 }, - deviceScaleFactor: 2, - }); - await loginAs(page, { appUrl, gammaUrl }, "admin"); - await expect( - page.getByRole("button").filter({ hasText: "[Stress] Pub preparations · 1" }).first(), - ).toBeVisible(); - await page.screenshot({ path: resolve(outputDirectory, "week.png"), animations: "disabled" }); - - await page.getByRole("tab", { name: "Month view", exact: true }).click(); - await expect(page.getByRole("button", { name: /\+\d+ more/ }).first()).toBeVisible(); - await page.screenshot({ path: resolve(outputDirectory, "month.png"), animations: "disabled" }); - - await page.getByRole("tab", { name: "List view", exact: true }).click(); - const stripes = page - .getByRole("button") - .filter({ hasText: "[Stress] Pub preparations · 1" }) - .first(); - await stripes.scrollIntoViewIfNeeded(); - await page.evaluate(() => window.scrollTo(0, 0)); - await page.screenshot({ path: resolve(outputDirectory, "list.png"), animations: "disabled" }); - - await page.getByRole("navigation").getByRole("link", { name: "Rules", exact: true }).click(); - await expect(page.getByRole("row")).toHaveCount(11); - await page.screenshot({ path: resolve(outputDirectory, "rules.png"), animations: "disabled" }); - console.log(`Saved four 3840 × 2234 screenshots to ${outputDirectory}`); - } finally { - await browser.close(); - } -} -void main().catch((error: unknown) => { - console.error(error); - process.exitCode = 1; -}); diff --git a/e2e/stress-data.ts b/e2e/stress-data.ts deleted file mode 100644 index e253ab0..0000000 --- a/e2e/stress-data.ts +++ /dev/null @@ -1,66 +0,0 @@ -import type { InputEvent, InputRule, Room } from "../frontend/src/generated/graphql"; - -export function stressData(now = new Date()) { - const monday = new Date(now); - monday.setDate(now.getDate() - ((now.getDay() + 6) % 7)); - monday.setHours(0, 0, 0, 0); - function dateAt(day: number) { - const date = new Date(monday); - date.setDate(date.getDate() + day); - return date; - } - function dateText(day: number) { - const date = dateAt(day); - return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`; - } - const rooms: Room[] = ["BIG_HUB", "GROUP_ROOM", "CTC", "THE_CLOUD"]; - const titles = ["Study session", "Project planning", "Committee meeting", "Workshop"]; - const events: InputEvent[] = []; - - function booking(day: number, hour: number, duration: number, title: string, room: Room[]) { - const start = dateAt(day); - start.setHours(hour, 0, 0, 0); - const end = new Date(start.getTime() + duration * 3_600_000); - events.push({ - title: `[Stress] ${title} · ${day + 1}`, - description: "Synthetic local stress-test booking. Safe to remove with the test environment.", - start: start.toISOString(), - end: end.toISOString(), - room, - phone: "0701234567", - booked_as: "digit", - booking_terms: true, - }); - } - - for (let day = 0; day < 14; day++) { - for (const [index, room] of rooms.entries()) { - for (const hour of [8, 10, 12, 14]) { - booking(day, hour, 1.5, `${titles[index]} ${hour}:00`, [room]); - } - } - booking( - day, - 17, - 7, - ["Pub preparations", "Karaoke brunch", "Milkshakepub"][day % 3]!, - rooms.slice(0, 3), - ); - booking(day, 18, 7, "Late-night games and society planning", ["THE_CLOUD"]); - } - - const rules: InputRule[] = Array.from({ length: 24 }, (_, index) => ({ - title: `[Stress] ${index % 2 ? "Reading morning" : "Room maintenance"} ${index + 1}`, - description: "Synthetic recurring rule for table pagination and layout checks.", - start_date: dateText(index), - end_date: dateText(index + 7), - start_time: "06:00", - end_time: "07:00", - priority: index + 1, - allow: false, - day_mask: 127, - room: rooms.slice(0, (index % 4) + 1), - })); - - return { events, rules }; -} diff --git a/e2e/stress-helpers.ts b/e2e/stress-helpers.ts deleted file mode 100644 index 768d1b7..0000000 --- a/e2e/stress-helpers.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { Page } from "@playwright/test"; -import { graphql } from "./booking-helpers"; -import { stressData } from "./stress-data"; - -export async function populateStress(page: Page, concurrency = 6) { - if (!Number.isInteger(concurrency) || concurrency < 1 || concurrency > 6) { - throw new Error("Concurrency must be 1–6"); - } - const existing = await graphql<{ - events: { title: string; start: string }[]; - rules: { title: string; start_date: string }[]; - }>(page, "{ events { title start } rules { title start_date } }"); - const data = stressData(); - const existingEvents = new Set(existing.events.map((event) => `${event.title}|${event.start}`)); - const existingRules = new Set( - existing.rules.map((rule) => `${rule.title}|${rule.start_date.slice(0, 10)}`), - ); - const events = data.events.filter( - (event) => !existingEvents.has(`${event.title}|${event.start}`), - ); - const rules = data.rules.filter((rule) => !existingRules.has(`${rule.title}|${rule.start_date}`)); - const timings: number[] = []; - - for (let offset = 0; offset < events.length; offset += concurrency) { - await Promise.all( - events.slice(offset, offset + concurrency).map(async (event) => { - const start = performance.now(); - const result = await graphql<{ createEvent: { en: string } | null }>( - page, - "mutation($event: InputEvent!) { createEvent(event: $event) { en } }", - { event }, - ); - if (result.createEvent) throw new Error(`${event.title}: ${result.createEvent.en}`); - timings.push(performance.now() - start); - }), - ); - } - for (const rule of rules) { - const result = await graphql<{ createRule: { en: string } | null }>( - page, - "mutation($rule: InputRule!) { createRule(rule: $rule) { en } }", - { rule }, - ); - if (result.createRule) throw new Error(result.createRule.en); - } - timings.sort((a, b) => a - b); - return { - createdEvents: events.length, - createdRules: rules.length, - concurrency, - writeP95Ms: timings[Math.floor(timings.length * 0.95)] ?? 0, - }; -} diff --git a/e2e/stress.spec.ts b/e2e/stress.spec.ts deleted file mode 100644 index f7b3990..0000000 --- a/e2e/stress.spec.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { test, expect } from "./fixtures"; -import { graphql } from "./booking-helpers"; -import { populateStress } from "./stress-helpers"; - -test.use({ role: "admin" }); - -test("a busy fortnight preserves bookings, diagonal stripes and rule pagination", async ({ - page, -}, testInfo) => { - const result = await populateStress(page); - expect(result.createdEvents).toBe(252); - expect(result.createdRules).toBe(24); - await testInfo.attach("stress-metrics.json", { - body: JSON.stringify(result), - contentType: "application/json", - }); - - const persisted = await graphql<{ events: { id: string }[]; rules: { id: string }[] }>( - page, - "{ events { id } rules { id } }", - ); - expect(persisted.events).toHaveLength(252); - expect(new Set(persisted.events.map((event) => event.id)).size).toBe(252); - expect(persisted.rules).toHaveLength(24); - - await page.reload(); - const stripedBooking = page - .getByRole("button") - .filter({ hasText: "[Stress] Pub preparations · 1" }) - .first(); - await expect(stripedBooking).toBeVisible(); - await expect(stripedBooking).toHaveCSS("background-image", /repeating-linear-gradient\(45deg/); - - await page.setViewportSize({ width: 1174, height: 1039 }); - const labelsFit = await stripedBooking.evaluate((card) => { - const bounds = card.getBoundingClientRect(); - const labels = [...card.querySelectorAll(".booking-event-label > *")]; - - return ( - labels.length === 2 && - labels.every((label) => { - const rect = label.getBoundingClientRect(); - return rect.left >= bounds.left && rect.right <= bounds.right; - }) - ); - }); - expect(labelsFit).toBe(true); - - await stripedBooking.click(); - const details = page.getByRole("dialog", { name: "Booking details", exact: true }); - await expect(details).toContainText("Storhubben"); - await expect(details).toContainText("Grupprummet"); - await expect(details).toContainText("CTC"); - await page.keyboard.press("Escape"); - - await page.getByRole("tab", { name: "Week view", exact: true }).focus(); - await page.keyboard.press("ArrowLeft"); - await expect(page.getByRole("tab", { name: "List view", exact: true })).toHaveAttribute( - "aria-selected", - "true", - ); - await expect(stripedBooking).toHaveCSS("background-image", /repeating-linear-gradient\(45deg/); - - await page.getByRole("tab", { name: "Month view", exact: true }).click(); - await page - .getByRole("button", { name: /\+\d+ more/ }) - .first() - .click(); - await expect(stripedBooking).toBeVisible(); - await expect(stripedBooking).toHaveCSS("background-image", /repeating-linear-gradient\(45deg/); - await stripedBooking.click(); - await expect(details).toBeVisible(); - await page.keyboard.press("Escape"); - - await page.getByRole("navigation").getByRole("link", { name: "Rules", exact: true }).click(); - await expect(page.getByRole("row")).toHaveCount(11); - await expect(page.getByText("Page 1 / 3", { exact: true })).toBeVisible(); - await page.getByRole("button", { name: "Next", exact: true }).click(); - await expect(page.getByText("Page 2 / 3", { exact: true })).toBeVisible(); - await page.getByRole("button", { name: "Next", exact: true }).click(); - await expect(page.getByRole("row")).toHaveCount(5); - await expect(page.getByRole("button", { name: "Next", exact: true })).toBeDisabled(); - - await page.setViewportSize({ width: 390, height: 844 }); - expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(390); - await page.getByRole("navigation").getByRole("link", { name: "Calendar", exact: true }).click(); - await page.getByRole("tab", { name: "List view", exact: true }).click(); - await expect(stripedBooking).toBeVisible(); - expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(390); -}); diff --git a/e2e/stress.ts b/e2e/stress.ts deleted file mode 100644 index 94e3fca..0000000 --- a/e2e/stress.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { chromium } from "@playwright/test"; -import { loginAs } from "./compose"; -import { populateStress } from "./stress-helpers"; - -async function main() { - const [appUrl, gammaUrl] = process.argv.slice(2); - if (!appUrl || !gammaUrl) - throw new Error("Usage: pnpm exec tsx e2e/stress.ts <local app URL> <local Gamma URL>"); - for (const url of [appUrl, gammaUrl]) { - if (!["localhost", "127.0.0.1", "gamma.localhost"].includes(new URL(url).hostname)) { - throw new Error("Stress fixtures may only be added to a localhost test environment"); - } - } - - const browser = await chromium.launch(); - try { - const page = await browser.newPage({ baseURL: appUrl }); - await loginAs(page, { appUrl, gammaUrl }, "admin"); - console.log(JSON.stringify(await populateStress(page, Number(process.argv[4] ?? 6)), null, 2)); - } finally { - await browser.close(); - } -} - -void main().catch((error: unknown) => { - console.error(error); - process.exitCode = 1; -}); diff --git a/package.json b/package.json index 489b401..5f78c18 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,7 @@ "format": "vp fmt", "format:check": "vp fmt --check", "typecheck": "pnpm --dir backend typecheck && pnpm --dir frontend typecheck && tsc --noEmit -p e2e/tsconfig.json", - "test:e2e": "playwright test", - "e2e:dev": "tsx e2e/dev.ts" + "test:e2e": "playwright test" }, "devDependencies": { "@graphql-codegen/core": "^6.2.0", From afa5476d1e7e00c74d20c88d8426ed900d8ec076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 11:49:06 +0200 Subject: [PATCH 13/41] Restore a concise README matching the original structure --- README.md | 197 +++++++++++------------------------------------------- 1 file changed, 38 insertions(+), 159 deletions(-) diff --git a/README.md b/README.md index fba80e2..a158785 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,23 @@ # bookIT -Booking service for Teknologsektionen Informationsteknik at Chalmers. +A booking service for the Chalmers Software Engineering Student Division (IT) -## Stack +## Contributors + +- [@molleer](https://github.com/molleer/) David 'Mölle' Möller +- [@x183](https://github.com/x183/) Oscar 'saxen' Palm +- [@Supergamer1337](https://github.com/Supergamer1337/) Felix 'sonic' Bjerhem Aronsson -- Node 24.19 LTS and pnpm 12.3.4, pinned in `.node-version` and `packageManager`. -- React 19, TypeScript, TanStack Router/Query, shadcn/ui and date-fns. -- FullCalendar 7.1 with its official Monarch shadcn registry components and the CTHIT blue/orange palette. -- Vite+ 0.3: Vite/Rolldown builds, Vitest tests, type-aware Oxlint and Oxfmt through `vp`. -- Express 5, Apollo Server 5, GraphQL 16 and Prisma 7 with the PostgreSQL driver adapter. -- Gamma OpenID Connect authentication; Redis-backed signed sessions. +## Requirements -GraphQL 16 is retained because Apollo Server 5 declares support for that major. -Prisma CLI and client stay on the same stable 7.10 release; the CLI's newer release-candidate tag is not mixed with the stable client. -Node's LTS line and matching Node types are deliberate runtime pins. +- [Docker](https://www.docker.com/) +- [Node.js](https://nodejs.org/en/) 24 (see `.node-version`) +- pnpm 12.3.4 +- A Gamma OAuth client and CLIENT API key for local authentication -## Development +## Setup -Install the pinned Node version, then from the repository root: +Dependencies, from the repository root: ```sh corepack enable @@ -25,164 +25,43 @@ corepack prepare pnpm@12.3.4 --activate pnpm install --frozen-lockfile pnpm codegen pnpm --dir backend exec prisma generate +docker compose up -d db redis +cp backend/.env.example backend/.env ``` -Install scripts are explicitly approved in `pnpm-workspace.yaml`. Review new blocked -scripts individually; do not globally enable dependency build scripts. - -With a local Gamma installation: +Configure Gamma's issuer URL, client credentials and API key in `backend/.env`. +Register `http://localhost:8080/api/callback` as the client's redirect URL and +set `SESSION_SECRET` using `openssl rand -hex 32`. -1. Start local backing services with `docker compose up -d db redis`. -2. Copy `backend/.env.example` to `backend/.env` and replace its placeholders. -3. Create a Gamma OAuth client with redirect URL `http://localhost:8080/api/callback`; - configure its client ID/secret and CLIENT API key in the environment. -4. Generate a strong session secret with `openssl rand -hex 32`. -5. Check that `DATABASE_URL` points only at your local database, then apply the schema - explicitly with `pnpm --dir backend migrate`. -6. Run `pnpm --dir frontend dev` and `pnpm --dir backend dev` in separate terminals. -7. Open `http://localhost:8080`, the backend's same-origin frontend proxy. - -The browser API is `/api/graphql/v1`; login/logout/callback are under `/api`. -`/api/health` is an unauthenticated readiness endpoint. Gamma's client-specific -`admin` authority grants administration. Active (non-alumni) Gamma groups grant -booking rights. Do not use production credentials or data for local tests. - -## Checks +Backend, after confirming `DATABASE_URL` points to your local database: ```sh -pnpm codegen -pnpm --dir backend exec prisma generate -pnpm typecheck -pnpm check -pnpm test -pnpm build -pnpm audit --audit-level low +pnpm --dir backend migrate +pnpm --dir backend dev ``` -`pnpm format` formats source/configuration with Oxfmt. `pnpm check` runs formatting, -type-aware lint and type checking; React hook rules apply only to frontend code, -not Playwright's unrelated fixture callbacks. Generated files are not hand-edited. - -The source of the API contract is `backend/src/schemas/v1/*.gql`. The supported -GraphQL Codegen core API generates backend resolver signatures and typed frontend -operation documents from this schema and `frontend/src/api/operations.graphql`. -Both generated outputs are committed; CI rejects stale output. - -## Browser verification - -With Docker running: +Frontend, in another terminal: ```sh -pnpm exec playwright install chromium -pnpm test:e2e +pnpm --dir frontend dev ``` -Playwright specs cover booking persistence, calendar filters and navigation, -administrator rules, and authorization. The extended -test fixture authenticates a fresh browser context by default, with configurable -admin/member/outsider identities. It uses real Gamma rather than mocked login. -One `e2e/compose.ts` owns all Testcontainers orchestration and separate databases. - -CI enforces contracts, types, lint/format, unit/API tests, builds, dependency audit, -the browser suite, and container builds. Browser failure artifacts are retained. -After quality checks, CI publishes uniquely tagged GHCR candidates and runs E2E -against their immutable digests. Successful main runs promote those same digests -to the existing commit-SHA and latest tags without rebuilding. PR runs never -update production tags. Fork/Dependabot PRs run quality checks only until reviewed -and moved to a trusted branch; they receive no registry credentials. -See [image-mode E2E](e2e/README.md#development-servers-or-published-images) for local -digest-based runs, routing, explicit schema initialization, and registry access. -Actions are SHA-pinned and Dependabot tracks future updates. +Open [http://localhost:8080](http://localhost:8080) to view the website. +The GraphQL endpoint is `/api/graphql/v1`. -## Containers and deployment +Run `pnpm check`, `pnpm test` and `pnpm build` for the project checks. +See [browser tests](e2e/README.md) for Playwright setup and CI image testing. -Build from the repository root: - -```sh -docker build -f backend/Dockerfile -t bookit-backend:local . -docker build -f frontend/Dockerfile -t bookit-frontend:local . -docker build -f db-scripts/Dockerfile -t bookit-cleanup:local db-scripts -``` +## Technologies -Backend port: 8080. Frontend Nginx port: 80. Configure the backend's -`FRONTEND_URL` to reach the frontend container. Expose the backend through the -trusted TLS reverse proxy so browser assets and API share an origin. Enable -`TRUST_PROXY=1` only when exactly one trusted proxy is in front. - -`prod.docker-compose.yml` builds both application images from the repository root -and connects them to BookIT's private database and Redis services. It uses an -existing Gamma issuer, not the retired bundled Gamma mock/development images. -Supply `DATABASE_URL`, `DB_PASS`, `ISSUER_BASE_URL`, `BASE_URL`, `CLIENT_ID`, -`CLIENT_SECRET`, `API_KEY`, and a strong `SESSION_SECRET` through your deployment -environment (or a protected Compose `--env-file`). For its bundled database, -`DATABASE_URL` must use host `db`, database `bookit`, user `postgres`, and the -URL-encoded `DB_PASS`. Register `${BASE_URL}/api/callback` in Gamma. The backend -binds only to host loopback for a trusted TLS reverse proxy; the other services -are not published. Set `TRUST_PROXY` for your actual proxy topology. - -Validate with `docker compose -f prod.docker-compose.yml config --quiet`, then -build with `docker compose -f prod.docker-compose.yml build`. The named -`bookit-db` volume is for new deployments: **do not switch an existing deployment -to it without mapping or migrating its current database storage first**. Back up -data and review the resolved Compose configuration before deploying. Schema -application remains an explicit release step; this file does not run it for you. - -Container startup no longer applies schema changes automatically. Before a release, -back up the database, review the Prisma schema diff and apply the approved schema -change explicitly using `pnpm --dir backend migrate` from a release checkout -(or `./node_modules/.bin/prisma db push` inside the backend image). -This repository still uses Prisma schema push, not a versioned migration history. -Never add `--accept-data-loss` as an unattended startup workaround. - -Session cookies are now signed; existing users will need to sign in again after -deployment. GraphQL dates now serialize explicitly as ISO strings. Mutation inputs -and IDs are non-null, and `deleteRule` returns the same localized error-or-null -contract as other mutations. Deploy frontend and backend together. - -Booking phone numbers are returned only to their original author or an administrator. -An authorized group edit may omit the phone number to preserve it; new bookings still -require one. Edits preserve the original author, and overlapping booking writes are -checked within a serializable transaction. - -If privacy cleanup has removed a booking's author, an authorized edit requires a -new phone number and records the editor as the new contact. It never restores the -deleted author's identity or reuses an unowned phone number. - -The privacy cleanup retains the existing SQL that clears phone numbers and -booking-user IDs 14 days after an event. Its unsupported Jobber image was replaced -with a small PostgreSQL-client container: cleanup runs on startup and every 24 -hours, retrying failures after one hour. It must target the correct application -database and be monitored separately. - -## Security and deferred infrastructure upgrades - -**PostgreSQL 12 and Redis 5 server upgrades are intentionally separate PRs.** -The development and E2E BookIT images pin PostgreSQL 12.22 and Redis 5.0.14 for -compatibility testing; pinning them does not restore security support. Confirm -actual production versions independently. Neither server should be publicly -exposed, and these follow-ups should not be treated as optional indefinitely. - -The modern Redis client explicitly uses RESP2 for Redis 5 compatibility. -Gamma's E2E PostgreSQL and Redis instances are separate from BookIT's; their pins -match the known working Gamma integration reference, not a production upgrade. - -Scoped dependency overrides address Prisma's vulnerable deepmerge-ts/MySQL2 -transitives and the deprecated Glob version pulled by Testcontainers' archive -helper. Remove those overrides once their parents support patched releases. -Code generation uses local schema files only, avoiding unused remote loaders and -their deprecated fetch polyfills. - -A clean package audit means no known advisories were reported for the lockfile -at that time. It does not certify application security or the deferred container -images. CI keeps auditing future runs; service image scanning and production -upgrade reviews remain necessary. - -The inherited rule schema has fixed February 2022 defaults for its creation/update -timestamps. The details view displays the stored values; correcting those defaults -and deciding how to repair historical metadata needs a separate schema review. +- [Docker](https://www.docker.com/) for development services and production containers +- [PostgreSQL](https://www.postgresql.org/) to store service data +- [Node.js](https://nodejs.org/en/) and [TypeScript](https://www.typescriptlang.org/) for the application +- [Express](https://expressjs.com/) and [Apollo Server](https://www.apollographql.com/docs/apollo-server/) for the GraphQL API +- [Prisma](https://www.prisma.io/docs/) to query the database and apply schema changes +- [React](https://react.dev/), [shadcn/ui](https://ui.shadcn.com/) and [FullCalendar](https://fullcalendar.io/) for the frontend +- [TanStack Router and Query](https://tanstack.com/) for routing and API data +- [Vite+](https://viteplus.dev/) for builds, tests, linting and formatting +- [Redis](https://redis.io/) to store user sessions -## Contributors - -- [@molleer](https://github.com/molleer/) David 'Mölle' Möller -- [@x183](https://github.com/x183/) Oscar 'saxen' Palm -- [@Supergamer1337](https://github.com/Supergamer1337/) Felix 'sonic' Bjerhem Aronsson +PostgreSQL 12 and Redis 5 server upgrades are deferred to separate PRs. From 86ba18bacc5153ac33e6ebdc990e400601a4305c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 11:56:33 +0200 Subject: [PATCH 14/41] Publish commit images and retag tested digests on GitHub releases --- .github/workflows/ci.yml | 81 ++++++++++++----------------------- .github/workflows/release.yml | 67 +++++++++++++++++++++++++++++ e2e/README.md | 32 +++++++------- 3 files changed, 111 insertions(+), 69 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b8677f..d0aad25 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: CI on: push: - branches: [main] + branches: ["**"] pull_request: branches: [main] workflow_dispatch: @@ -20,6 +20,7 @@ env: jobs: quality: name: Types, lint, tests, build, and dependency audit + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository runs-on: ubuntu-24.04 timeout-minutes: 20 steps: @@ -58,10 +59,10 @@ jobs: if: github.event_name == 'pull_request' && (github.event.pull_request.head.repo.full_name != github.repository || github.actor == 'dependabot[bot]') run: echo "Image publishing and image E2E are skipped for fork/Dependabot PRs. After reviewing the changes, a maintainer must put them on a trusted branch in this repository to run the image pipeline. No registry credentials are supplied to this job." >> "$GITHUB_STEP_SUMMARY" - candidates: - name: Publish candidate images + images: + name: Publish commit images needs: quality - if: github.event_name != 'pull_request' || (github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]') + if: github.event_name != 'pull_request' && github.actor != 'dependabot[bot]' runs-on: ubuntu-24.04 timeout-minutes: 30 permissions: @@ -75,21 +76,21 @@ jobs: with: persist-credentials: false - uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - - name: Candidate names + - name: Image names id: names shell: bash run: | repository="ghcr.io/${GITHUB_REPOSITORY,,}" echo "frontend=${repository}-frontend" >> "$GITHUB_OUTPUT" echo "backend=${repository}-backend" >> "$GITHUB_OUTPUT" - echo "tag=candidate-${GITHUB_SHA}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" >> "$GITHUB_OUTPUT" - - name: Log in to GHCR for candidate publication + echo "tag=${GITHUB_SHA}" >> "$GITHUB_OUTPUT" + - name: Log in to GHCR for commit publication uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Publish frontend candidate + - name: Publish frontend commit id: frontend uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: @@ -100,7 +101,7 @@ jobs: tags: ${{ steps.names.outputs.frontend }}:${{ steps.names.outputs.tag }} cache-from: type=gha,scope=frontend cache-to: type=gha,mode=max,scope=frontend - - name: Publish backend candidate + - name: Publish backend commit id: backend uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: @@ -113,8 +114,8 @@ jobs: cache-to: type=gha,mode=max,scope=backend e2e: - name: Browser flows against published candidates - needs: candidates + name: Browser flows against published images + needs: images runs-on: ubuntu-24.04 timeout-minutes: 30 permissions: @@ -122,8 +123,8 @@ jobs: packages: read env: E2E_MODE: images - BOOKIT_FRONTEND_IMAGE: ${{ needs.candidates.outputs.frontend }} - BOOKIT_BACKEND_IMAGE: ${{ needs.candidates.outputs.backend }} + BOOKIT_FRONTEND_IMAGE: ${{ needs.images.outputs.frontend }} + BOOKIT_BACKEND_IMAGE: ${{ needs.images.outputs.backend }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -144,7 +145,7 @@ jobs: run: pnpm exec playwright install --with-deps chromium - name: Verify Docker is available for Testcontainers run: docker info - - name: Log in to GHCR for candidate pulls + - name: Log in to GHCR for commit pulls uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: registry: ghcr.io @@ -158,6 +159,19 @@ jobs: run: | docker buildx imagetools create --dry-run --prefer-index=false "$BOOKIT_FRONTEND_IMAGE" > /dev/null docker buildx imagetools create --dry-run --prefer-index=false "$BOOKIT_BACKEND_IMAGE" > /dev/null + - name: Record tested image digests + shell: bash + run: | + jq -n --arg sha "$GITHUB_SHA" \ + --arg frontend "$BOOKIT_FRONTEND_IMAGE" --arg backend "$BOOKIT_BACKEND_IMAGE" \ + '{sha: $sha, frontend: $frontend, backend: $backend}' > tested-images.json + - name: Save tested image digests for releases + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: tested-images-${{ github.run_attempt }} + path: tested-images.json + retention-days: 90 + if-no-files-found: error - name: Preserve failing browser traces, screenshots, videos, and logs if: failure() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -169,42 +183,3 @@ jobs: e2e-run.log if-no-files-found: ignore retention-days: 7 - - promote: - name: Promote tested digests on main - needs: [candidates, e2e] - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - runs-on: ubuntu-24.04 - timeout-minutes: 30 - permissions: - contents: read - packages: write - env: - BOOKIT_FRONTEND_IMAGE: ${{ needs.candidates.outputs.frontend }} - BOOKIT_BACKEND_IMAGE: ${{ needs.candidates.outputs.backend }} - steps: - - uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - - name: Log in to GitHub Container Registry - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Refuse to promote an outdated main run - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - shell: bash - run: test "$(gh api "repos/$GITHUB_REPOSITORY/git/ref/heads/main" --jq .object.sha)" = "$GITHUB_SHA" - - name: Tag the tested manifests without rebuilding - shell: bash - run: | - for image in "$BOOKIT_FRONTEND_IMAGE" "$BOOKIT_BACKEND_IMAGE"; do - repository="${image%@*}" - digest="${image##*@}" - docker buildx imagetools create --prefer-index=false \ - --tag "$repository:$GITHUB_SHA" --tag "$repository:latest" "$image" - for tag in "$GITHUB_SHA" latest; do - actual="$(docker buildx imagetools inspect "$repository:$tag" --format '{{.Manifest.Digest}}')" - test "$actual" = "$digest" - done - done diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a201714 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,67 @@ +name: Release images + +on: + release: + types: [published] + +permissions: + contents: read + actions: read + packages: write + +concurrency: + group: release-${{ github.event.release.tag_name }} + cancel-in-progress: false + +jobs: + release: + name: Tag tested images + runs-on: ubuntu-24.04 + timeout-minutes: 10 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_TAG: ${{ github.event.release.tag_name }} + steps: + - name: Load the release commit's successful CI image digests + shell: bash + run: | + if [[ ! "$RELEASE_TAG" =~ ^[a-zA-Z0-9_][a-zA-Z0-9_.-]{0,127}$ || + "$RELEASE_TAG" == latest || "$RELEASE_TAG" =~ ^[a-f0-9]{40}$ ]]; then + echo "Release tags must be valid Docker tags, not latest or a commit SHA." >&2 + exit 1 + fi + run="$(gh api --method GET "repos/$GITHUB_REPOSITORY/actions/workflows/ci.yml/runs" \ + -f head_sha="$GITHUB_SHA" -f event=push -f status=success -f per_page=1 \ + --jq '.workflow_runs[0] | select(. != null) | [.id, .run_attempt] | @tsv')" + if [[ -z "$run" ]]; then + echo "No successful push CI run for $GITHUB_SHA. Wait for CI, then rerun this release workflow." >&2 + exit 1 + fi + read -r run_id attempt <<< "$run" + gh run download "$run_id" --repo "$GITHUB_REPOSITORY" \ + --name "tested-images-$attempt" --dir tested + jq -e --arg sha "$GITHUB_SHA" '.sha == $sha' tested/tested-images.json + - uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 + - name: Log in to GHCR + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Apply the release tag without rebuilding + shell: bash + run: | + for component in frontend backend; do + image="$(jq -er --arg component "$component" '.[$component]' tested/tested-images.json)" + repository="ghcr.io/${GITHUB_REPOSITORY,,}-$component" + digest="${image##*@}" + [[ "$image" == "$repository@$digest" && "$digest" =~ ^sha256:[a-f0-9]{64}$ ]] + docker buildx imagetools inspect "$image" > /dev/null + done + for component in frontend backend; do + image="$(jq -r --arg component "$component" '.[$component]' tested/tested-images.json)" + target="${image%@*}:$RELEASE_TAG" + docker buildx imagetools create --prefer-index=false --tag "$target" "$image" + actual="$(docker buildx imagetools inspect "$target" --format '{{.Manifest.Digest}}')" + test "$actual" = "${image##*@}" + done diff --git a/e2e/README.md b/e2e/README.md index 1c9d304..6c86ccc 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -17,7 +17,7 @@ Local runs default to development servers (`E2E_MODE=dev`). CI requires `E2E_MODE=images` and refuses to fall back to development servers if either image is missing. -To run the same suite locally against specific published candidates: +To run the same suite locally against specific published images: ```sh # For private packages, authenticate Docker with read:packages access first. @@ -28,7 +28,7 @@ BOOKIT_BACKEND_IMAGE='ghcr.io/cthit/bookit-node-backend@sha256:<backend-digest>' pnpm test:e2e ``` -Replace each placeholder with its 64-character digest from the CI candidate job +Replace each placeholder with its 64-character digest from the CI image job outputs/logs. Tags such as `latest` are rejected. Docker/Testcontainers use the host's Docker credential store; credentials are not forwarded into containers. Public images do not require a login. @@ -52,28 +52,28 @@ are involved. ## CI image lifecycle and fork PRs -Quality checks run first. Trusted runs then publish frontend/backend candidates -to GHCR, tagged `candidate-<commit>-<run-id>-<attempt>`. PRs use GitHub's checked-out -merge commit, so the candidates contain exactly the source that quality checks -examined. Build outputs pass immutable `repository@sha256:...` references to E2E. -The candidate and E2E jobs have only package-write and package-read permissions, -respectively, plus checkout read access; login credentials are cleaned up by the -login action. +Each branch push runs quality checks, publishes frontend/backend images to GHCR +as `:<commit-sha>`, then runs E2E against their immutable digests. Same-repository +PRs use that branch CI; fork PRs run quality checks separately. Publication has +package-write permission; E2E has package-read permission. -After image E2E passes on a push to main, a separate job copies those manifests to -the commit-SHA and `latest` tags and verifies the resulting digests. It never -rebuilds. It refuses promotion if main has advanced. PRs and manual dispatches -publish candidates only and never update production tags. E2E also dry-runs the -manifest-copy operation without writing registry tags. +Publishing a GitHub Release copies the successful push CI run's tested digests +to the release tag (for example, `v1.2.0`). No rebuild, main-merge promotion, or +`latest` update occurs. Release tags must be valid Docker tags, not `latest` or +commit SHAs. Prereleases use their exact release tag too. + +The tested-digest artifact is retained for 90 days. Missing or expired evidence +blocks release tagging: rerun CI for that commit, then rerun the release workflow. +E2E dry-runs the manifest-copy operation without writing registry tags. Fork PRs and Dependabot PRs run quality checks only. They receive no registry -credentials and skip candidate publication/image E2E. After reviewing their code, +credentials and skip publication/image E2E. After reviewing their code, a maintainer must place it on a trusted branch in this repository to get image coverage before merging. We do not use `pull_request_target` to execute PR code. GHCR packages must grant this repository's Actions token write/read access; this may need configuring for pre-existing packages. -Candidate tags are retained for debugging; apply a registry retention policy +Commit tags are retained; apply a registry retention policy separately without removing digests referenced by production tags. Failures retain the E2E console log (including image references and startup logs), browser traces, screenshots, video, and bounded per-container logs. Normal completion and failure From 2ad433f96410805d2589552cfedacfc0a1e79347 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 12:01:45 +0200 Subject: [PATCH 15/41] Remove CodeQL workflow for now --- .github/workflows/codeql.yml | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index e39bee6..0000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: CodeQL -on: - push: - branches: [main] - pull_request: - branches: [main] - schedule: - - cron: "17 5 * * 2" - workflow_dispatch: -permissions: - contents: read -jobs: - analyze: - runs-on: ubuntu-24.04 - timeout-minutes: 20 - permissions: - contents: read - security-events: write - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 - with: - languages: javascript-typescript - build-mode: none - queries: security-extended - - uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 - with: - category: /language:javascript-typescript From 74ac3458135afad51fd78ac573e1595a011f5edd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 12:07:08 +0200 Subject: [PATCH 16/41] Use commit image tags directly in E2E and releases --- .github/workflows/ci.yml | 43 +++++----------------------------- .github/workflows/release.yml | 34 ++++++++++----------------- e2e/README.md | 24 ++++++++++--------- e2e/application-images.test.ts | 42 +++++++++++++++++++++++---------- e2e/application-images.ts | 10 ++++---- e2e/compose.ts | 13 ++++++++-- 6 files changed, 77 insertions(+), 89 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0aad25..03ede56 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,8 +11,8 @@ permissions: contents: read concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + group: images-${{ github.sha }} + cancel-in-progress: false env: CI: true @@ -68,22 +68,11 @@ jobs: permissions: contents: read packages: write - outputs: - frontend: ${{ steps.names.outputs.frontend }}@${{ steps.frontend.outputs.digest }} - backend: ${{ steps.names.outputs.backend }}@${{ steps.backend.outputs.digest }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - - name: Image names - id: names - shell: bash - run: | - repository="ghcr.io/${GITHUB_REPOSITORY,,}" - echo "frontend=${repository}-frontend" >> "$GITHUB_OUTPUT" - echo "backend=${repository}-backend" >> "$GITHUB_OUTPUT" - echo "tag=${GITHUB_SHA}" >> "$GITHUB_OUTPUT" - name: Log in to GHCR for commit publication uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: @@ -91,25 +80,23 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Publish frontend commit - id: frontend uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: context: . file: frontend/Dockerfile platforms: linux/amd64 push: true - tags: ${{ steps.names.outputs.frontend }}:${{ steps.names.outputs.tag }} + tags: ghcr.io/cthit/bookit-node-frontend:${{ github.sha }} cache-from: type=gha,scope=frontend cache-to: type=gha,mode=max,scope=frontend - name: Publish backend commit - id: backend uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: context: . file: backend/Dockerfile platforms: linux/amd64 push: true - tags: ${{ steps.names.outputs.backend }}:${{ steps.names.outputs.tag }} + tags: ghcr.io/cthit/bookit-node-backend:${{ github.sha }} cache-from: type=gha,scope=backend cache-to: type=gha,mode=max,scope=backend @@ -123,8 +110,8 @@ jobs: packages: read env: E2E_MODE: images - BOOKIT_FRONTEND_IMAGE: ${{ needs.images.outputs.frontend }} - BOOKIT_BACKEND_IMAGE: ${{ needs.images.outputs.backend }} + BOOKIT_FRONTEND_IMAGE: ghcr.io/cthit/bookit-node-frontend:${{ github.sha }} + BOOKIT_BACKEND_IMAGE: ghcr.io/cthit/bookit-node-backend:${{ github.sha }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -154,24 +141,6 @@ jobs: - name: Run authenticated browser flows shell: bash run: pnpm test:e2e 2>&1 | tee e2e-run.log - - name: Dry-run digest promotion - shell: bash - run: | - docker buildx imagetools create --dry-run --prefer-index=false "$BOOKIT_FRONTEND_IMAGE" > /dev/null - docker buildx imagetools create --dry-run --prefer-index=false "$BOOKIT_BACKEND_IMAGE" > /dev/null - - name: Record tested image digests - shell: bash - run: | - jq -n --arg sha "$GITHUB_SHA" \ - --arg frontend "$BOOKIT_FRONTEND_IMAGE" --arg backend "$BOOKIT_BACKEND_IMAGE" \ - '{sha: $sha, frontend: $frontend, backend: $backend}' > tested-images.json - - name: Save tested image digests for releases - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: tested-images-${{ github.run_attempt }} - path: tested-images.json - retention-days: 90 - if-no-files-found: error - name: Preserve failing browser traces, screenshots, videos, and logs if: failure() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a201714..65a3649 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,7 +10,7 @@ permissions: packages: write concurrency: - group: release-${{ github.event.release.tag_name }} + group: images-${{ github.sha }} cancel-in-progress: false jobs: @@ -22,7 +22,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} RELEASE_TAG: ${{ github.event.release.tag_name }} steps: - - name: Load the release commit's successful CI image digests + - name: Require successful CI for the release commit shell: bash run: | if [[ ! "$RELEASE_TAG" =~ ^[a-zA-Z0-9_][a-zA-Z0-9_.-]{0,127}$ || @@ -30,17 +30,13 @@ jobs: echo "Release tags must be valid Docker tags, not latest or a commit SHA." >&2 exit 1 fi - run="$(gh api --method GET "repos/$GITHUB_REPOSITORY/actions/workflows/ci.yml/runs" \ - -f head_sha="$GITHUB_SHA" -f event=push -f status=success -f per_page=1 \ - --jq '.workflow_runs[0] | select(. != null) | [.id, .run_attempt] | @tsv')" - if [[ -z "$run" ]]; then - echo "No successful push CI run for $GITHUB_SHA. Wait for CI, then rerun this release workflow." >&2 + conclusion="$(gh api --method GET "repos/$GITHUB_REPOSITORY/actions/workflows/ci.yml/runs" \ + -f head_sha="$GITHUB_SHA" -f per_page=100 \ + --jq '[.workflow_runs[] | select(.event == "push" or .event == "workflow_dispatch")][0].conclusion')" + if [[ "$conclusion" != success ]]; then + echo "The latest CI run for $GITHUB_SHA must pass before releasing." >&2 exit 1 fi - read -r run_id attempt <<< "$run" - gh run download "$run_id" --repo "$GITHUB_REPOSITORY" \ - --name "tested-images-$attempt" --dir tested - jq -e --arg sha "$GITHUB_SHA" '.sha == $sha' tested/tested-images.json - uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - name: Log in to GHCR uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 @@ -52,16 +48,10 @@ jobs: shell: bash run: | for component in frontend backend; do - image="$(jq -er --arg component "$component" '.[$component]' tested/tested-images.json)" - repository="ghcr.io/${GITHUB_REPOSITORY,,}-$component" - digest="${image##*@}" - [[ "$image" == "$repository@$digest" && "$digest" =~ ^sha256:[a-f0-9]{64}$ ]] - docker buildx imagetools inspect "$image" > /dev/null - done - for component in frontend backend; do - image="$(jq -r --arg component "$component" '.[$component]' tested/tested-images.json)" - target="${image%@*}:$RELEASE_TAG" - docker buildx imagetools create --prefer-index=false --tag "$target" "$image" + repository="ghcr.io/cthit/bookit-node-$component" + digest="$(docker buildx imagetools inspect "$repository:$GITHUB_SHA" --format '{{.Manifest.Digest}}')" + target="$repository:$RELEASE_TAG" + docker buildx imagetools create --prefer-index=false --tag "$target" "$repository@$digest" actual="$(docker buildx imagetools inspect "$target" --format '{{.Manifest.Digest}}')" - test "$actual" = "${image##*@}" + test "$actual" = "$digest" done diff --git a/e2e/README.md b/e2e/README.md index 6c86ccc..359da7e 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -23,17 +23,18 @@ To run the same suite locally against specific published images: # For private packages, authenticate Docker with read:packages access first. docker login ghcr.io E2E_MODE=images \ -BOOKIT_FRONTEND_IMAGE='ghcr.io/cthit/bookit-node-frontend@sha256:<frontend-digest>' \ -BOOKIT_BACKEND_IMAGE='ghcr.io/cthit/bookit-node-backend@sha256:<backend-digest>' \ +BOOKIT_FRONTEND_IMAGE='ghcr.io/cthit/bookit-node-frontend:<commit-sha>' \ +BOOKIT_BACKEND_IMAGE='ghcr.io/cthit/bookit-node-backend:<commit-sha>' \ pnpm test:e2e ``` -Replace each placeholder with its 64-character digest from the CI image job -outputs/logs. Tags such as `latest` are rejected. Docker/Testcontainers use the +Use the full 40-character commit SHA. Image digests (`@sha256:<64 hex characters>`) +also work; tags such as `latest` are rejected. Images are pulled on each run. +Docker/Testcontainers use the host's Docker credential store; credentials are not forwarded into containers. Public images do not require a login. -Image mode runs the exact backend digest once with `prisma db push` against the +Image mode runs the backend image once with `prisma db push` against the empty, isolated BookIT database, and checks its exit status before starting the application. It does not generate a client from the checkout or migrate on normal backend startup. No production database is touched. @@ -53,18 +54,19 @@ are involved. ## CI image lifecycle and fork PRs Each branch push runs quality checks, publishes frontend/backend images to GHCR -as `:<commit-sha>`, then runs E2E against their immutable digests. Same-repository +as `:<commit-sha>`. E2E waits for publication, then pulls those commit tags directly. +Same-repository PRs use that branch CI; fork PRs run quality checks separately. Publication has package-write permission; E2E has package-read permission. -Publishing a GitHub Release copies the successful push CI run's tested digests -to the release tag (for example, `v1.2.0`). No rebuild, main-merge promotion, or +Publishing a GitHub Release requires the latest CI run for its commit to pass, +then copies the commit images to the release tag (for example, `v1.2.0`). +No rebuild, main-merge promotion, or `latest` update occurs. Release tags must be valid Docker tags, not `latest` or commit SHAs. Prereleases use their exact release tag too. -The tested-digest artifact is retained for 90 days. Missing or expired evidence -blocks release tagging: rerun CI for that commit, then rerun the release workflow. -E2E dry-runs the manifest-copy operation without writing registry tags. +CI and releases share a per-commit concurrency group so publication and release +tagging cannot race. There is no image-metadata artifact to download. Fork PRs and Dependabot PRs run quality checks only. They receive no registry credentials and skip publication/image E2E. After reviewing their code, diff --git a/e2e/application-images.test.ts b/e2e/application-images.test.ts index aad03dc..0cbc1fb 100644 --- a/e2e/application-images.test.ts +++ b/e2e/application-images.test.ts @@ -24,18 +24,36 @@ describe("E2E application mode", () => { ).toEqual({ frontend, backend }); }); - it.each([undefined, "ghcr.io/cthit/bookit-node-backend:latest", "backend@sha256:abc"])( - "rejects missing or mutable image references: %s", - (value) => { - expect(() => - applicationImages({ - E2E_MODE: "images", - BOOKIT_FRONTEND_IMAGE: frontend, - BOOKIT_BACKEND_IMAGE: value, - }), - ).toThrow("BOOKIT_BACKEND_IMAGE must be"); - }, - ); + it("accepts commit tags from CI", () => { + const commit = "c".repeat(40); + const images = { + frontend: `ghcr.io/cthit/bookit-node-frontend:${commit}`, + backend: `ghcr.io/cthit/bookit-node-backend:${commit}`, + }; + expect( + applicationImages({ + E2E_MODE: "images", + CI: "true", + BOOKIT_FRONTEND_IMAGE: images.frontend, + BOOKIT_BACKEND_IMAGE: images.backend, + }), + ).toEqual(images); + }); + + it.each([ + undefined, + "ghcr.io/cthit/bookit-node-backend:latest", + "ghcr.io/cthit/bookit-node-backend:abc1234", + "backend@sha256:abc", + ])("rejects missing or unpinned image references: %s", (value) => { + expect(() => + applicationImages({ + E2E_MODE: "images", + BOOKIT_FRONTEND_IMAGE: frontend, + BOOKIT_BACKEND_IMAGE: value, + }), + ).toThrow("BOOKIT_BACKEND_IMAGE must use"); + }); it("does not silently ignore image references in development mode", () => { expect(() => applicationImages({ BOOKIT_FRONTEND_IMAGE: frontend })).toThrow( diff --git a/e2e/application-images.ts b/e2e/application-images.ts index 7370d5e..02caeb5 100644 --- a/e2e/application-images.ts +++ b/e2e/application-images.ts @@ -16,15 +16,15 @@ export function applicationImages( return undefined; } - const digestImage = (name: string): string => { + const image = (name: string): string => { const value = env[name]; - if (!value || !/^[a-z0-9][a-z0-9._:/-]*@sha256:[a-f0-9]{64}$/.test(value)) - throw new Error(`${name} must be a registry image pinned with @sha256:<64 hex characters>`); + if (!value || !/^[a-z0-9][a-z0-9._:/-]*(?::[a-f0-9]{40}|@sha256:[a-f0-9]{64})$/.test(value)) + throw new Error(`${name} must use a full commit-SHA tag or an @sha256 image digest`); return value; }; return { - frontend: digestImage("BOOKIT_FRONTEND_IMAGE"), - backend: digestImage("BOOKIT_BACKEND_IMAGE"), + frontend: image("BOOKIT_FRONTEND_IMAGE"), + backend: image("BOOKIT_BACKEND_IMAGE"), }; } diff --git a/e2e/compose.ts b/e2e/compose.ts index 47de860..d6e491d 100644 --- a/e2e/compose.ts +++ b/e2e/compose.ts @@ -1,6 +1,12 @@ import { expect, type Browser, type Page } from "@playwright/test"; import { PostgreSqlContainer, type StartedPostgreSqlContainer } from "@testcontainers/postgresql"; -import { GenericContainer, Network, Wait, type StartedTestContainer } from "testcontainers"; +import { + GenericContainer, + Network, + PullPolicy, + Wait, + type StartedTestContainer, +} from "testcontainers"; import { spawn, type ChildProcess } from "node:child_process"; import { createServer, type AddressInfo } from "node:net"; import { resolve } from "node:path"; @@ -356,9 +362,10 @@ export async function compose(browser: Browser): Promise<Environment> { if (appImages) { record("images", JSON.stringify(appImages)); console.log(`Using published BookIT images: ${JSON.stringify(appImages)}`); - console.log("Initializing the isolated database with the candidate backend image..."); + console.log("Initializing the isolated database with the published backend image..."); await track( new GenericContainer(appImages.backend) + .withPullPolicy(PullPolicy.alwaysPull()) .withPlatform("linux/amd64") .withNetwork(network) .withEnvironment({ DATABASE_URL: env.DATABASE_URL }) @@ -372,6 +379,7 @@ export async function compose(browser: Browser): Promise<Environment> { ); await track( new GenericContainer(appImages.frontend) + .withPullPolicy(PullPolicy.alwaysPull()) .withPlatform("linux/amd64") .withNetwork(network) .withNetworkAliases("bookit-frontend") @@ -385,6 +393,7 @@ export async function compose(browser: Browser): Promise<Environment> { ); await track( new GenericContainer(appImages.backend) + .withPullPolicy(PullPolicy.alwaysPull()) .withPlatform("linux/amd64") .withNetwork(network) .withEnvironment(env) From f15dd2eb957daa10e176aa10c5fea075255c16ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 12:09:39 +0200 Subject: [PATCH 17/41] Remove Dependabot configuration for now --- .github/dependabot.yml | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 1c35bad..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,21 +0,0 @@ -version: 2 -updates: - - package-ecosystem: github-actions - directory: / - schedule: - interval: weekly - groups: - actions: - patterns: ["*"] - - package-ecosystem: npm - directory: / - schedule: - interval: weekly - open-pull-requests-limit: 5 - groups: - prisma: - patterns: ["prisma", "@prisma/*"] - graphql-codegen: - patterns: ["@graphql-codegen/*"] - react: - patterns: ["react", "react-dom", "@types/react", "@types/react-dom"] From de059b491f7a20fdec8a452b6e7409a0223de032 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 12:10:26 +0200 Subject: [PATCH 18/41] Remove booking transaction unit tests covered in part by E2E --- backend/src/tests/booking-transaction.test.ts | 226 ------------------ 1 file changed, 226 deletions(-) delete mode 100644 backend/src/tests/booking-transaction.test.ts diff --git a/backend/src/tests/booking-transaction.test.ts b/backend/src/tests/booking-transaction.test.ts deleted file mode 100644 index 88a9b9e..0000000 --- a/backend/src/tests/booking-transaction.test.ts +++ /dev/null @@ -1,226 +0,0 @@ -import { PrismaClient, Prisma } from "@prisma/client"; -import { PrismaPg } from "@prisma/adapter-pg"; -import { afterAll, afterEach, describe, expect, it, vi } from "vite-plus/test"; -import { createEvent, editEvent, withBookingTransaction } from "../services/event.service"; -import type { User } from "../models/user"; -import type { Event } from "../models/event"; - -const prisma = new PrismaClient({ - adapter: new PrismaPg({ connectionString: "postgresql://test:test@127.0.0.1:1/test" }), -}); -const conflict = () => - new Prisma.PrismaClientKnownRequestError("Concurrent write", { - code: "P2034", - clientVersion: "7.10.0", - }); -const driverConflict = () => - Object.assign( - new Error("TransactionWriteConflict", { cause: { kind: "TransactionWriteConflict" } }), - { - name: "DriverAdapterError", - }, - ); - -describe("Booking transaction recovery", () => { - afterEach(() => vi.restoreAllMocks()); - afterAll(() => prisma.$disconnect()); - - const user: User = { - cid: "editor", - groups: ["digit"], - is_admin: false, - sub: "editor", - sid: "", - given_name: "", - locale: "en", - picture: "", - name: "Editor", - nickname: "Editor", - family_name: "", - jti: "", - }; - const input: Event = { - id: "booking", - title: "Edited booking", - start: new Date().toISOString(), - end: new Date(Date.now() + 3_600_000).toISOString(), - booked_as: "digit", - booked_by: "editor", - booking_terms: true, - room: ["GROUP_ROOM"], - }; - it.each([undefined, null, "0707654321"])( - "preserves the author's identity and handles edit phone %s", - async (phone) => { - const previous = { - id: "booking", - title: "Booking", - description: "", - booked_by: "author", - booked_as: "digit", - phone: "0701234567", - room: ["GROUP_ROOM"], - start: new Date(input.start), - end: new Date(input.end), - created_at: new Date(), - updated_at: new Date(), - }; - vi.spyOn(prisma, "$transaction").mockImplementation(async (operation) => operation(prisma)); - vi.spyOn(prisma.event, "findUnique").mockResolvedValue(previous); - vi.spyOn(prisma.event, "count").mockResolvedValue(0); - vi.spyOn(prisma.rule, "findMany").mockResolvedValue([]); - const update = vi.spyOn(prisma.event, "update").mockResolvedValue(previous); - expect(await editEvent(prisma, { ...input, phone }, user)).toBeNull(); - expect(update).toHaveBeenCalledWith( - expect.objectContaining({ - data: expect.objectContaining({ - phone: phone ?? "0701234567", - booked_by: "author", - title: "Edited booking", - }), - }), - ); - }, - ); - const anonymized = { - id: "booking", - title: "Old booking", - description: "", - booked_by: "", - booked_as: "digit", - phone: "", - room: ["GROUP_ROOM"], - start: new Date(Date.now() - 21 * 86_400_000), - end: new Date(Date.now() - 20 * 86_400_000), - created_at: new Date(Date.now() - 30 * 86_400_000), - updated_at: new Date(Date.now() - 30 * 86_400_000), - }; - - it.each([false, true])( - "lets an authorized editor become the contact for an anonymized booking (admin: %s)", - async (is_admin) => { - vi.spyOn(prisma, "$transaction").mockImplementation(async (operation) => operation(prisma)); - vi.spyOn(prisma.event, "findUnique").mockResolvedValue(anonymized); - vi.spyOn(prisma.event, "count").mockResolvedValue(0); - vi.spyOn(prisma.rule, "findMany").mockResolvedValue([]); - const update = vi.spyOn(prisma.event, "update").mockResolvedValue(anonymized); - - expect( - await editEvent( - prisma, - { ...input, phone: "0707654321", booked_by: "untrusted-author" }, - { ...user, is_admin, groups: is_admin ? [] : user.groups }, - ), - ).toBeNull(); - expect(update).toHaveBeenCalledWith( - expect.objectContaining({ - data: expect.objectContaining({ - booked_by: user.cid, - phone: "0707654321", - start: new Date(input.start), - end: new Date(input.end), - }), - }), - ); - }, - ); - - it.each([undefined, null, "", "invalid"])( - "requires a fresh valid phone for an anonymized booking, not %s", - async (phone) => { - vi.spyOn(prisma, "$transaction").mockImplementation(async (operation) => operation(prisma)); - vi.spyOn(prisma.event, "findUnique").mockResolvedValue({ - ...anonymized, - phone: "0701234567", - }); - vi.spyOn(prisma.event, "count").mockResolvedValue(0); - const update = vi.spyOn(prisma.event, "update"); - - expect(await editEvent(prisma, { ...input, phone }, user)).toMatchObject({ - en: "Provided phone number is faulty", - }); - expect(update).not.toHaveBeenCalled(); - }, - ); - - it("does not let an outsider take over an anonymized booking", async () => { - vi.spyOn(prisma, "$transaction").mockImplementation(async (operation) => operation(prisma)); - vi.spyOn(prisma.event, "findUnique").mockResolvedValue(anonymized); - const update = vi.spyOn(prisma.event, "update"); - - expect( - await editEvent(prisma, { ...input, phone: "0707654321" }, { ...user, groups: [] }), - ).toMatchObject({ en: "You do not have permission to edit this event" }); - expect(update).not.toHaveBeenCalled(); - }); - - it("still rejects new bookings without a phone", async () => { - vi.spyOn(prisma, "$transaction").mockImplementation(async (operation) => operation(prisma)); - vi.spyOn(prisma.event, "count").mockResolvedValue(0); - const create = vi.spyOn(prisma.event, "create"); - expect(await createEvent(prisma, { ...input, id: undefined }, user)).toMatchObject({ - en: "Provided phone number is faulty", - }); - expect(create).not.toHaveBeenCalled(); - }); - - it("retries serialization conflicts using serializable isolation", async () => { - const transaction = vi - .spyOn(prisma, "$transaction") - .mockRejectedValueOnce(conflict()) - .mockResolvedValue(null); - expect(await withBookingTransaction(prisma, async () => null)).toBeNull(); - expect(transaction).toHaveBeenCalledTimes(2); - expect(transaction).toHaveBeenCalledWith( - expect.any(Function), - expect.objectContaining({ isolationLevel: "Serializable" }), - ); - }); - - it("bounds retries and returns a useful error when contention persists", async () => { - const transaction = vi.spyOn(prisma, "$transaction").mockRejectedValue(conflict()); - expect(await withBookingTransaction(prisma, async () => null)).toMatchObject({ - en: "A booking changed concurrently. Please try again.", - }); - expect(transaction).toHaveBeenCalledTimes(5); - }); - - it("retries the PostgreSQL adapter's direct commit conflict and returns the rechecked overlap", async () => { - const overlap = { en: "The time slot is already taken", sv: "Den angivna tiden är upptagen" }; - const transaction = vi - .spyOn(prisma, "$transaction") - .mockRejectedValueOnce(driverConflict()) - .mockResolvedValue(overlap); - expect(await withBookingTransaction(prisma, async () => null)).toEqual(overlap); - expect(transaction).toHaveBeenCalledTimes(2); - }); - - it("bounds retries of direct driver conflicts too", async () => { - const transaction = vi.spyOn(prisma, "$transaction").mockRejectedValue(driverConflict()); - expect(await withBookingTransaction(prisma, async () => null)).toMatchObject({ - en: "A booking changed concurrently. Please try again.", - }); - expect(transaction).toHaveBeenCalledTimes(5); - }); - - it.each([ - new Error("TransactionWriteConflict"), - Object.assign(new Error("DatabaseAccessDenied", { cause: { kind: "DatabaseAccessDenied" } }), { - name: "DriverAdapterError", - }), - ])("does not mistake other errors for a structured driver conflict", async (error) => { - const transaction = vi.spyOn(prisma, "$transaction").mockRejectedValue(error); - await expect(withBookingTransaction(prisma, async () => null)).rejects.toBe(error); - expect(transaction).toHaveBeenCalledTimes(1); - }); - - it("does not retry unrelated database failures", async () => { - const transaction = vi - .spyOn(prisma, "$transaction") - .mockRejectedValue(new Error("Connection failed")); - await expect(withBookingTransaction(prisma, async () => null)).rejects.toThrow( - "Connection failed", - ); - expect(transaction).toHaveBeenCalledTimes(1); - }); -}); From fb58353681550a73a767c6d3e96828f73d7e8641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 12:10:49 +0200 Subject: [PATCH 19/41] Remove GraphQL unit tests while retaining E2E API coverage --- backend/src/tests/graphql.test.ts | 136 ------------------------------ 1 file changed, 136 deletions(-) delete mode 100644 backend/src/tests/graphql.test.ts diff --git a/backend/src/tests/graphql.test.ts b/backend/src/tests/graphql.test.ts deleted file mode 100644 index 7218b09..0000000 --- a/backend/src/tests/graphql.test.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { join } from "node:path"; -import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vite-plus/test"; -import { ApolloServer } from "@apollo/server"; -import { PrismaClient } from "@prisma/client"; -import { PrismaPg } from "@prisma/adapter-pg"; -import { loadFilesSync } from "@graphql-tools/load-files"; -import { mergeTypeDefs } from "@graphql-tools/merge"; -import { getResolvers } from "../resolvers"; -import type { Context } from "../utils/commonTypes"; - -// An unreachable database ensures validation failures do not query it. -const prisma = new PrismaClient({ - adapter: new PrismaPg({ connectionString: "postgresql://test:test@127.0.0.1:1/test" }), -}); -const server = new ApolloServer<Context>({ - typeDefs: mergeTypeDefs(loadFilesSync(join(__dirname, "../schemas/v1/*.gql"))), - resolvers: getResolvers({ prisma }), -}); -const contextValue: Context = { - user: { - sub: "user-id", - cid: "member", - name: "Member", - nickname: "Member", - given_name: "", - family_name: "", - sid: "", - locale: "en", - picture: "", - jti: "", - groups: [], - is_admin: false, - }, -}; - -describe("Apollo GraphQL contract", () => { - afterEach(() => vi.restoreAllMocks()); - it.each([ - { cid: "member", is_admin: false, expected: null }, - { cid: "author", is_admin: false, expected: "0701234567" }, - { cid: "member", is_admin: true, expected: "0701234567" }, - ])( - "protects phones across every booking query for $cid / admin=$is_admin", - async ({ cid, is_admin, expected }) => { - const event = { - id: "booking", - title: "Booking", - description: "", - booked_by: "author", - booked_as: "digit", - phone: "0701234567", - room: ["GROUP_ROOM"], - start: new Date(), - end: new Date(), - created_at: new Date(), - updated_at: new Date(), - }; - vi.spyOn(prisma.event, "findFirst").mockResolvedValue(event); - vi.spyOn(prisma.event, "findMany").mockResolvedValue([event]); - const result = await server.executeOperation( - { - query: `{ - event(id: "booking") { phone } - events { phone } - eventsFT(from: "2026-09-01", to: "2026-09-30") { phone } - }`, - }, - { contextValue: { user: { ...contextValue.user, cid, is_admin, groups: ["digit"] } } }, - ); - if (result.body.kind !== "single") throw new Error("Unexpected incremental response"); - expect(result.body.singleResult.errors).toBeUndefined(); - expect(result.body.singleResult.data).toEqual({ - event: { phone: expected }, - events: [{ phone: expected }], - eventsFT: [{ phone: expected }], - }); - }, - ); - it("rejects a supplied ID when creating a new booking before checking availability", async () => { - const result = await server.executeOperation( - { - query: `mutation { createEvent(event: { - id: "00000000-0000-0000-0000-000000000001", title: "Spoofed booking", - start: "2026-09-10T10:00:00Z", end: "2026-09-10T11:00:00Z", room: [GROUP_ROOM], - phone: "0701234567", booked_as: "digit", booking_terms: true - }) { en } }`, - }, - { contextValue }, - ); - if (result.body.kind !== "single") throw new Error("Unexpected incremental response"); - expect(result.body.singleResult.errors).toBeUndefined(); - expect(result.body.singleResult.data?.createEvent).toMatchObject({ - en: "New bookings must not specify an existing booking ID", - }); - }); - beforeAll(() => server.start()); - afterAll(async () => { - await server.stop(); - await prisma.$disconnect(); - }); - - it("rejects null mutation input through schema validation", async () => { - const result = await server.executeOperation( - { query: "mutation { createEvent(event: null) { en } }" }, - { contextValue }, - ); - expect(result.body.kind).toBe("single"); - if (result.body.kind === "single") - expect(result.body.singleResult.errors?.[0].message).toContain("InputEvent!"); - }); - - it("returns a typed permission error when a member attempts to delete rules", async () => { - const result = await server.executeOperation( - { query: 'mutation { deleteRule(id: "00000000-0000-0000-0000-000000000001") { en sv } }' }, - { contextValue }, - ); - if (result.body.kind !== "single") throw new Error("Unexpected incremental response"); - expect(result.body.singleResult.errors).toBeUndefined(); - expect(result.body.singleResult.data?.deleteRule).toMatchObject({ - en: "You do not have permission to delete rules", - }); - }); - - it("returns the authenticated user's server-side roles and groups", async () => { - const result = await server.executeOperation( - { query: "{ user { cid is_admin groups } }" }, - { contextValue }, - ); - if (result.body.kind !== "single") throw new Error("Unexpected incremental response"); - expect(result.body.singleResult.data?.user).toEqual({ - cid: "member", - is_admin: false, - groups: [], - }); - }); -}); From f9e571de05c1aa8004f552f22461e68dd6089a49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 12:11:11 +0200 Subject: [PATCH 20/41] Remove session store unit tests --- backend/src/tests/session-store.test.ts | 66 ------------------------- 1 file changed, 66 deletions(-) delete mode 100644 backend/src/tests/session-store.test.ts diff --git a/backend/src/tests/session-store.test.ts b/backend/src/tests/session-store.test.ts deleted file mode 100644 index f1edcd0..0000000 --- a/backend/src/tests/session-store.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { afterEach, describe, expect, it, vi } from "vite-plus/test"; -import { createSessionStore, type SessionRedis } from "../auth/session-store"; - -const redis = () => ({ - get: vi.fn<SessionRedis["get"]>(), - set: vi.fn<SessionRedis["set"]>().mockResolvedValue("OK"), - del: vi.fn<SessionRedis["del"]>().mockResolvedValue(1), -}); - -describe("OIDC Redis session adapter", () => { - afterEach(() => vi.useRealTimers()); - - it("stores session data using the OIDC expiration and invokes the callback", async () => { - vi.useFakeTimers(); - vi.setSystemTime(1_000_000); - const client = redis(); - const store = createSessionStore(client); - const payload: Parameters<typeof store.set>[1] = { - header: { iat: 1000, uat: 1000, exp: 1060 }, - data: { - id_token: "test", - access_token: "test", - refresh_token: "", - token_type: "Bearer", - expires_at: "1060", - }, - cookie: { expires: 1_060_000, maxAge: 60_000 }, - }; - await new Promise<void>((resolve, reject) => - store.set("session", payload, (error) => (error ? reject(error) : resolve())), - ); - expect(client.set).toHaveBeenCalledWith("bookit:oidc:session", JSON.stringify(payload), { - EX: 60, - }); - }); - - it("does not restore an expired session even if the Redis key remains", async () => { - const client = redis(); - client.get.mockResolvedValue(JSON.stringify({ header: { exp: 1 }, data: {} })); - const store = createSessionStore(client); - const value = await new Promise((resolve, reject) => - store.get("old", (error, session) => (error ? reject(error) : resolve(session))), - ); - expect(value).toBeNull(); - }); - - it("reports Redis errors instead of silently hanging an authentication request", async () => { - const client = redis(); - client.get.mockRejectedValue(new Error("Redis unavailable")); - const store = createSessionStore(client); - await expect( - new Promise((resolve, reject) => - store.get("session", (error, session) => (error ? reject(error) : resolve(session))), - ), - ).rejects.toThrow("Redis unavailable"); - }); - - it("removes the session on logout", async () => { - const client = redis(); - const store = createSessionStore(client); - await new Promise<void>((resolve, reject) => - store.destroy("session", (error) => (error ? reject(error) : resolve())), - ); - expect(client.del).toHaveBeenCalledWith("bookit:oidc:session"); - }); -}); From 79449778e1303a6beeccbb2e65176a6fb345266e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 12:12:49 +0200 Subject: [PATCH 21/41] Require braces and multiline formatting for control statements --- backend/src/auth/session-store.ts | 4 +++- backend/src/index.ts | 12 +++++++--- backend/src/services/event.service.ts | 28 ++++++++++++++++++------ backend/src/services/rule.service.ts | 19 +++++++++++----- e2e/application-images.ts | 14 ++++++++---- e2e/booking-helpers.ts | 8 +++++-- e2e/booking-lifecycle.spec.ts | 4 +++- e2e/calendar.spec.ts | 8 +++++-- e2e/compose.ts | 21 +++++++++++++----- e2e/fixtures.ts | 4 +++- frontend/src/api/client.ts | 18 ++++++++++----- frontend/src/components/booking-form.tsx | 13 +++++++---- frontend/src/lib/calendar-blocks.ts | 4 +++- frontend/src/lib/dates.ts | 4 +++- frontend/src/lib/user.ts | 4 +++- frontend/src/main.tsx | 4 +++- frontend/src/pages/booking.tsx | 16 ++++++++++---- frontend/src/pages/calendar.tsx | 8 +++++-- frontend/src/pages/rules.tsx | 26 ++++++++++++++++------ frontend/src/router.tsx | 3 ++- vite.config.ts | 1 + 21 files changed, 164 insertions(+), 59 deletions(-) diff --git a/backend/src/auth/session-store.ts b/backend/src/auth/session-store.ts index 087a46e..ad66258 100644 --- a/backend/src/auth/session-store.ts +++ b/backend/src/auth/session-store.ts @@ -17,7 +17,9 @@ export function createSessionStore(client: SessionRedis): OidcStore { void client .get(key(sid)) .then((value) => { - if (!value) return null; + if (!value) { + return null; + } const payload = JSON.parse(value) as Payload; return payload.header.exp > Date.now() / 1000 ? payload : null; }) diff --git a/backend/src/index.ts b/backend/src/index.ts index c5054be..078d144 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -15,7 +15,9 @@ interface GammaGroup { const requiredEnvironment = (name: string): string => { const value = process.env[name]; - if (!value) throw new Error(`Missing required environment variable: ${name}`); + if (!value) { + throw new Error(`Missing required environment variable: ${name}`); + } return value; }; @@ -23,7 +25,9 @@ async function main() { process.env.TZ ??= "Europe/Stockholm"; const app = express(); app.disable("x-powered-by"); - if (process.env.TRUST_PROXY === "1") app.set("trust proxy", 1); + if (process.env.TRUST_PROXY === "1") { + app.set("trust proxy", 1); + } const httpServer = createServer(app); const redis = createClient({ // Redis 5 does not support RESP3. @@ -94,7 +98,9 @@ async function main() { console.log(`BookIT listening on port ${port}`); let stopping = false; const stop = () => { - if (stopping) return; + if (stopping) { + return; + } stopping = true; void apollo .stop() diff --git a/backend/src/services/event.service.ts b/backend/src/services/event.service.ts index ee7f877..6e59b5c 100644 --- a/backend/src/services/event.service.ts +++ b/backend/src/services/event.service.ts @@ -142,7 +142,9 @@ const validEvent = async ( }; } } catch (e) { - if (isSerializationFailure(e)) throw e; + if (isSerializationFailure(e)) { + throw e; + } console.log(e); return { sv: "Kunde inte kontrollera överlappande bokningar", @@ -179,7 +181,9 @@ const toEvent = (event: Event) => ({ }); const isSerializationFailure = (error: unknown): boolean => { - if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2034") return true; + if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2034") { + return true; + } // Driver-adapter commit failures can bypass Prisma's P2034 wrapper. return ( error instanceof globalThis.Error && @@ -205,8 +209,12 @@ export const withBookingTransaction = async ( timeout: 10_000, }); } catch (error) { - if (!isSerializationFailure(error)) throw error; - if (attempt + 1 < maxAttempts) await delay(20 * 2 ** attempt + Math.random() * 30); + if (!isSerializationFailure(error)) { + throw error; + } + if (attempt + 1 < maxAttempts) { + await delay(20 * 2 ** attempt + Math.random() * 30); + } } } return { @@ -230,7 +238,9 @@ export const editEvent = async ( const id = event.id; return withBookingTransaction(prisma, async (transaction) => { const previous = await transaction.event.findUnique({ where: { id } }); - if (!previous) return { sv: "Kunde inte hämta gamla bokningen", en: "Failed to get event" }; + if (!previous) { + return { sv: "Kunde inte hämta gamla bokningen", en: "Failed to get event" }; + } if (!userIsInBookingGroup(previous, user)) { return { sv: "Du har inte behörighet att redigera denna bokning", @@ -245,7 +255,9 @@ export const editEvent = async ( }; const error = (await validEvent(transaction, updated, user)) || (await checkRules(transaction, updated)); - if (error) return error; + if (error) { + return error; + } await transaction.event.update({ where: { id }, data: toEvent(updated) }); return null; }); @@ -265,7 +277,9 @@ export const createEvent = async ( return withBookingTransaction(prisma, async (transaction) => { const error = (await validEvent(transaction, event, user)) || (await checkRules(transaction, event)); - if (error) return error; + if (error) { + return error; + } await transaction.event.create({ data: toEvent(event) }); return null; }); diff --git a/backend/src/services/rule.service.ts b/backend/src/services/rule.service.ts index e6de1c5..0f6efd8 100644 --- a/backend/src/services/rule.service.ts +++ b/backend/src/services/rule.service.ts @@ -47,14 +47,18 @@ const insertRule = (explicitRules: ExplicitRule[], current: Date, rule: rule): v export const toExplicitRules = (rules: rule[], from: Date, to: Date): ExplicitRule[] => { const explicitRules: ExplicitRule[] = []; - if (!Number.isFinite(from.getTime()) || !Number.isFinite(to.getTime()) || from > to) return []; + if (!Number.isFinite(from.getTime()) || !Number.isFinite(to.getTime()) || from > to) { + return []; + } for (const rule of rules) { const current = new Date(Math.max(from.getTime(), rule.start_date.getTime())); const end = new Date(Math.min(to.getTime(), rule.end_date.getTime())); current.setHours(0, 0, 0, 0); end.setHours(0, 0, 0, 0); while (current <= end) { - if (dayApplies(current, rule.day_mask)) insertRule(explicitRules, current, rule); + if (dayApplies(current, rule.day_mask)) { + insertRule(explicitRules, current, rule); + } current.setDate(current.getDate() + 1); } } @@ -71,8 +75,12 @@ const mergeIntoList = ( rule: ExplicitRule, [nextMergedRule, ...mergedRules]: ExplicitRule[], ): ExplicitRule[] => { - if (rule.start >= rule.end) return nextMergedRule ? [nextMergedRule, ...mergedRules] : []; - if (nextMergedRule == undefined) return [rule]; + if (rule.start >= rule.end) { + return nextMergedRule ? [nextMergedRule, ...mergedRules] : []; + } + if (nextMergedRule == undefined) { + return [rule]; + } if (nextMergedRule.start > rule.start) { if (nextMergedRule.start >= rule.end) { return [rule, nextMergedRule, ...mergedRules]; @@ -85,8 +93,9 @@ const mergeIntoList = ( ]), ]; } - if (nextMergedRule.end > rule.start) + if (nextMergedRule.end > rule.start) { return [nextMergedRule, ...mergeIntoList({ ...rule, start: nextMergedRule.end }, mergedRules)]; + } return [nextMergedRule, ...mergeIntoList(rule, mergedRules)]; }; diff --git a/e2e/application-images.ts b/e2e/application-images.ts index 02caeb5..391fc19 100644 --- a/e2e/application-images.ts +++ b/e2e/application-images.ts @@ -7,19 +7,25 @@ export function applicationImages( env: NodeJS.ProcessEnv = process.env, ): ApplicationImages | undefined { const mode = env.E2E_MODE ?? "dev"; - if (mode !== "dev" && mode !== "images") throw new Error("E2E_MODE must be dev or images"); - if (env.CI && mode !== "images") throw new Error("CI E2E requires E2E_MODE=images"); + if (mode !== "dev" && mode !== "images") { + throw new Error("E2E_MODE must be dev or images"); + } + if (env.CI && mode !== "images") { + throw new Error("CI E2E requires E2E_MODE=images"); + } if (mode === "dev") { - if (env.BOOKIT_FRONTEND_IMAGE || env.BOOKIT_BACKEND_IMAGE) + if (env.BOOKIT_FRONTEND_IMAGE || env.BOOKIT_BACKEND_IMAGE) { throw new Error("Set E2E_MODE=images when supplying BookIT image references"); + } return undefined; } const image = (name: string): string => { const value = env[name]; - if (!value || !/^[a-z0-9][a-z0-9._:/-]*(?::[a-f0-9]{40}|@sha256:[a-f0-9]{64})$/.test(value)) + if (!value || !/^[a-z0-9][a-z0-9._:/-]*(?::[a-f0-9]{40}|@sha256:[a-f0-9]{64})$/.test(value)) { throw new Error(`${name} must use a full commit-SHA tag or an @sha256 image digest`); + } return value; }; diff --git a/e2e/booking-helpers.ts b/e2e/booking-helpers.ts index cfb0df0..b8169c5 100644 --- a/e2e/booking-helpers.ts +++ b/e2e/booking-helpers.ts @@ -66,7 +66,9 @@ export async function openBooking(page: Page, title: string): Promise<string> { ); const id = result.events.find((event) => event.title === title)?.id; - if (!id) throw new Error("Booking details did not include a booking id"); + if (!id) { + throw new Error("Booking details did not include a booking id"); + } await page.goto(`/bookings/${id}`); await expect(page.getByRole("heading", { name: title, exact: true })).toBeVisible(); @@ -88,7 +90,9 @@ export async function graphql<T>( const body = (await response.json()) as { data?: T; errors?: unknown[] }; expect(body.errors).toBeUndefined(); - if (!body.data) throw new Error("GraphQL response has no data"); + if (!body.data) { + throw new Error("GraphQL response has no data"); + } return body.data; } diff --git a/e2e/booking-lifecycle.spec.ts b/e2e/booking-lifecycle.spec.ts index d8334b6..c7d5014 100644 --- a/e2e/booking-lifecycle.spec.ts +++ b/e2e/booking-lifecycle.spec.ts @@ -91,7 +91,9 @@ test("simultaneous requests cannot reserve the same room and time twice", async expect(concurrentBookings).toHaveLength(1); const winner = concurrentBookings[0]; - if (!winner) throw new Error("The successful concurrent booking was not persisted"); + if (!winner) { + throw new Error("The successful concurrent booking was not persisted"); + } const removed = await graphql<DeleteBookingMutation>( page, diff --git a/e2e/calendar.spec.ts b/e2e/calendar.spec.ts index a4c8d55..26b4211 100644 --- a/e2e/calendar.spec.ts +++ b/e2e/calendar.spec.ts @@ -43,7 +43,9 @@ test("editing immediately after a calendar drag preserves the moved times", asyn }>(page, "{ events { title start end } }"); const booking = events.find((entry) => entry.title === title); - if (!booking) throw new Error("Calendar booking is missing"); + if (!booking) { + throw new Error("Calendar booking is missing"); + } return booking; }; @@ -56,7 +58,9 @@ test("editing immediately after a calendar drag preserves the moved times", asyn await event.first().scrollIntoViewIfNeeded(); const box = await event.first().boundingBox(); - if (!box) throw new Error("Calendar booking has no drag target"); + if (!box) { + throw new Error("Calendar booking has no drag target"); + } const movedResponse = page.waitForResponse( (response) => diff --git a/e2e/compose.ts b/e2e/compose.ts index d6e491d..a118eeb 100644 --- a/e2e/compose.ts +++ b/e2e/compose.ts @@ -92,7 +92,9 @@ async function unusedPort(): Promise<number> { } async function stopProcess(child: ChildProcess): Promise<void> { - if (!child.pid || child.exitCode !== null || child.signalCode !== null) return; + if (!child.pid || child.exitCode !== null || child.signalCode !== null) { + return; + } const exited = new Promise<void>((resolve) => child.once("exit", () => resolve())); process.kill(-child.pid, "SIGTERM"); @@ -203,7 +205,9 @@ export async function compose(browser: Browser): Promise<Environment> { }; const stop = async () => { - if (stopped) return; + if (stopped) { + return; + } stopped = true; const failures: unknown[] = []; @@ -229,7 +233,9 @@ export async function compose(browser: Browser): Promise<Environment> { failures.push(error); } - if (failures.length) throw new AggregateError(failures, "E2E environment cleanup failed"); + if (failures.length) { + throw new AggregateError(failures, "E2E environment cleanup failed"); + } }; const startProcess = (name: string, args: string[], env: Record<string, string>) => { @@ -245,8 +251,9 @@ export async function compose(browser: Browser): Promise<Environment> { child.stderr?.on("data", (chunk: Buffer) => record(name, chunk)); child.on("error", (error) => record(name, error.message)); child.on("exit", (code) => { - if (code !== null && code !== 0 && !stopped) + if (code !== null && code !== 0 && !stopped) { console.error(`[${name}] exited with code ${code}\n${logs[name] ?? ""}`); + } }); return child; @@ -484,13 +491,15 @@ export async function compose(browser: Browser): Promise<Environment> { "TRUNCATE TABLE event, rule;", ]); - if (result.exitCode !== 0) + if (result.exitCode !== 0) { throw new Error(`Could not reset isolated BookIT database: ${result.output}`); + } }, }; } catch (error) { - for (const [name, log] of Object.entries(logs)) + for (const [name, log] of Object.entries(logs)) { console.error(`[${name}]\n${log.slice(-8_000)}`); + } await stop(); throw error; diff --git a/e2e/fixtures.ts b/e2e/fixtures.ts index 6c464cd..854e7d3 100644 --- a/e2e/fixtures.ts +++ b/e2e/fixtures.ts @@ -46,7 +46,9 @@ export const test = base.extend<TestFixtures, { environment: Environment }>({ ], page: async ({ page, environment, role, authenticate }, use) => { - if (authenticate) await loginAs(page, environment, role); + if (authenticate) { + await loginAs(page, environment, role); + } await use(page); }, diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 491303a..c4a5cc6 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -11,13 +11,19 @@ export async function request<T, V extends Record<string, unknown>>( headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: print(document), variables }), }); - if (response.status === 401 || response.redirected) + if (response.status === 401 || response.redirected) { throw new Error("Your session expired. Please sign in again."); - if (!response.ok) throw new Error(`Request failed (${response.status}). Please try again.`); + } + if (!response.ok) { + throw new Error(`Request failed (${response.status}). Please try again.`); + } const result = (await response.json()) as { data?: T; errors?: { message: string }[] }; - if (result.errors?.length) + if (result.errors?.length) { throw new Error(result.errors.map((error) => error.message).join(". ")); - if (!result.data) throw new Error("The server returned no data."); + } + if (!result.data) { + throw new Error("The server returned no data."); + } return result.data; } @@ -25,5 +31,7 @@ export function checkMutation( error: { en: string; sv: string } | null | undefined, language: "en" | "sv", ) { - if (error) throw new Error(error[language]); + if (error) { + throw new Error(error[language]); + } } diff --git a/frontend/src/components/booking-form.tsx b/frontend/src/components/booking-form.tsx index 4f15f1b..0d67683 100644 --- a/frontend/src/components/booking-form.tsx +++ b/frontend/src/components/booking-form.tsx @@ -67,15 +67,19 @@ export function BookingForm({ mutationFn: async (form: FormData) => { const from = new Date(formText(form, "start")); const to = new Date(formText(form, "end")); - if (!(to > from)) + if (!(to > from)) { throw new Error( t("End time must be after start time.", "Sluttiden måste vara efter starttiden."), ); - if (!selected.length) throw new Error(t("Select at least one room.", "Välj minst ett rum.")); - if (!terms || !privacy || !notification) + } + if (!selected.length) { + throw new Error(t("Select at least one room.", "Välj minst ett rum.")); + } + if (!terms || !privacy || !notification) { throw new Error( t("Please confirm all three booking conditions.", "Bekräfta alla tre bokningsvillkor."), ); + } const event = { ...(booking?.id ? { id: booking.id } : {}), title: formText(form, "title").trim(), @@ -108,7 +112,7 @@ export function BookingForm({ setError(""); mutation.mutate(new FormData(event.currentTarget)); } - if (!eligibleGroups.length) + if (!eligibleGroups.length) { return ( <p role="alert" className="rounded-lg bg-amber-50 p-5"> {t( @@ -117,6 +121,7 @@ export function BookingForm({ )} </p> ); + } return ( <form onSubmit={submit} className="space-y-7"> <div className="grid gap-5 sm:grid-cols-2"> diff --git a/frontend/src/lib/calendar-blocks.ts b/frontend/src/lib/calendar-blocks.ts index 930c3ba..3957ff0 100644 --- a/frontend/src/lib/calendar-blocks.ts +++ b/frontend/src/lib/calendar-blocks.ts @@ -30,7 +30,9 @@ export function calendarBlocks(slots: readonly BlockedSlot[]): CalendarBlock[] { const start = boundaries[index - 1]!; const end = boundaries[index]!; const active = intervals.filter((slot) => slot.start <= start && slot.end >= end); - if (!active.length) continue; + if (!active.length) { + continue; + } const title = [...new Set(active.map((slot) => slot.title))].sort().join(" · "); const description = [ diff --git a/frontend/src/lib/dates.ts b/frontend/src/lib/dates.ts index 625b568..797eca1 100644 --- a/frontend/src/lib/dates.ts +++ b/frontend/src/lib/dates.ts @@ -1,7 +1,9 @@ import { format, isValid } from "date-fns"; export function parseDate(value: string): Date { const date = /^\d+$/.test(value) ? new Date(Number(value)) : new Date(value); - if (!isValid(date)) throw new Error("Invalid date received from the server."); + if (!isValid(date)) { + throw new Error("Invalid date received from the server."); + } return date; } export function localInput(value: Date) { diff --git a/frontend/src/lib/user.ts b/frontend/src/lib/user.ts index 8e54d17..2513f94 100644 --- a/frontend/src/lib/user.ts +++ b/frontend/src/lib/user.ts @@ -5,7 +5,9 @@ export const userOptions = queryOptions({ queryKey: ["user"], queryFn: async () => { const { user } = await request(CurrentUserDocument, {}); - if (!user) throw new Error("Please sign in to book a room."); + if (!user) { + throw new Error("Please sign in to book a room."); + } return user; }, staleTime: 60_000, diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 93e974e..8cc0c80 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -8,7 +8,9 @@ import { LanguageProvider } from "./lib/language"; import "./styles.css"; const client = new QueryClient({ defaultOptions: { queries: { retry: 1, staleTime: 20_000 } } }); const root = document.getElementById("root"); -if (!root) throw new Error("Application root is missing"); +if (!root) { + throw new Error("Application root is missing"); +} createRoot(root).render( <StrictMode> <QueryClientProvider client={client}> diff --git a/frontend/src/pages/booking.tsx b/frontend/src/pages/booking.tsx index 1674e5c..9354d4d 100644 --- a/frontend/src/pages/booking.tsx +++ b/frontend/src/pages/booking.tsx @@ -78,10 +78,16 @@ export function BookingDetails({ }, onError: (error) => toast.error(error.message), }); - if (query.isPending) return <Loading />; - if (query.error) return <Failure error={query.error} />; + if (query.isPending) { + return <Loading />; + } + if (query.error) { + return <Failure error={query.error} />; + } const booking = query.data.event; - if (!booking) return <p role="alert">{t("Booking not found.", "Bokningen hittades inte.")}</p>; + if (!booking) { + return <p role="alert">{t("Booking not found.", "Bokningen hittades inte.")}</p>; + } const canEdit = user?.is_admin || user?.groups?.includes(booking.booked_as); return ( <div className="max-w-3xl mx-auto"> @@ -103,7 +109,9 @@ export function BookingDetails({ if (onClose) { onClose(); void navigate({ to: "/edit-event", search: { id } }); - } else setEditing(!editing); + } else { + setEditing(!editing); + } }} > <Pencil className="size-4" /> diff --git a/frontend/src/pages/calendar.tsx b/frontend/src/pages/calendar.tsx index b7d77a8..c4b11c7 100644 --- a/frontend/src/pages/calendar.tsx +++ b/frontend/src/pages/calendar.tsx @@ -167,7 +167,9 @@ export function CalendarPage() { }) } eventClick={(info) => { - if (info.event.id) setDetailId(info.event.id); + if (info.event.id) { + setDetailId(info.event.id); + } }} eventDrop={(info) => { const booking = bookings.find((event) => event.id === info.event.id); @@ -223,7 +225,9 @@ export function CalendarPage() { <Dialog open={detailId !== null} onOpenChange={(open) => { - if (!open) setDetailId(null); + if (!open) { + setDetailId(null); + } }} > <DialogContent diff --git a/frontend/src/pages/rules.tsx b/frontend/src/pages/rules.tsx index 0e88405..b128d6e 100644 --- a/frontend/src/pages/rules.tsx +++ b/frontend/src/pages/rules.tsx @@ -54,9 +54,15 @@ type SortKey = | "allow"; function sortValue(rule: RuleDetails, key: SortKey): string | number { - if (key === "priority" || key === "day_mask") return rule[key] ?? 0; - if (key === "allow") return Number(rule.allow); - if (key === "room") return rule.room?.map(roomName).sort().join(", ") ?? ""; + if (key === "priority" || key === "day_mask") { + return rule[key] ?? 0; + } + if (key === "allow") { + return Number(rule.allow); + } + if (key === "room") { + return rule.room?.map(roomName).sort().join(", ") ?? ""; + } return rule[key] ?? ""; } @@ -113,10 +119,11 @@ export function RulesPage() { } const create = useMutation({ mutationFn: async (form: FormData) => { - if (!selected.length || !days) + if (!selected.length || !days) { throw new Error( t("Select rooms and at least one weekday.", "Välj rum och minst en veckodag."), ); + } const rule = { title: formText(form, "title").trim(), description: formText(form, "description"), @@ -129,10 +136,11 @@ export function RulesPage() { start_time: formText(form, "start_time").slice(0, 5), end_time: formText(form, "end_time").slice(0, 5), }; - if (rule.end_date < rule.start_date || rule.end_time <= rule.start_time) + if (rule.end_date < rule.start_date || rule.end_time <= rule.start_time) { throw new Error( t("The rule must end after it starts.", "Regeln måste sluta efter att den börjar."), ); + } const result = await request(CreateRuleDocument, { rule }); checkMutation(result.createRule, language); }, @@ -298,7 +306,9 @@ export function RulesPage() { <Dialog open={Boolean(details)} onOpenChange={(open) => { - if (!open) setDetailsId(null); + if (!open) { + setDetailsId(null); + } }} > <DialogContent className="sm:max-w-2xl max-h-[90vh] overflow-y-auto"> @@ -440,7 +450,9 @@ export function RulesPage() { <Dialog open={Boolean(deleting)} onOpenChange={(open) => { - if (!open) setDeleting(null); + if (!open) { + setDeleting(null); + } }} > <DialogContent> diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx index 0c18613..4d25df0 100644 --- a/frontend/src/router.tsx +++ b/frontend/src/router.tsx @@ -143,13 +143,14 @@ const legacyEditRoute = createRoute({ id: typeof search.id === "string" ? search.id : undefined, }), beforeLoad: ({ search }) => { - if (search.id) + if (search.id) { throw redirect({ to: "/bookings/$id", params: { id: search.id }, search: { edit: true }, replace: true, }); + } throw redirect({ to: "/", replace: true }); }, }); diff --git a/vite.config.ts b/vite.config.ts index 16b9618..c480ea2 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -6,6 +6,7 @@ export default defineConfig({ }, lint: { plugins: ["typescript", "react", "jsx-a11y", "import", "unicorn", "oxc"], + rules: { curly: ["error", "all"] }, overrides: [ { files: ["frontend/src/**/*.ts", "frontend/src/**/*.tsx"], From 4f599c76df3bb61f68764e70ed3fc2e3a3f03b39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 12:20:58 +0200 Subject: [PATCH 22/41] Limit stored OIDC session data and exposed user fields --- backend/src/auth/session-store.ts | 11 ++++++++++- backend/src/auth/user.ts | 7 +------ backend/src/generated/schema.ts | 14 ++------------ backend/src/models/user.ts | 7 +------ backend/src/schemas/v1/user.gql | 7 +------ 5 files changed, 15 insertions(+), 31 deletions(-) diff --git a/backend/src/auth/session-store.ts b/backend/src/auth/session-store.ts index ad66258..9ecda5c 100644 --- a/backend/src/auth/session-store.ts +++ b/backend/src/auth/session-store.ts @@ -30,8 +30,17 @@ export function createSessionStore(client: SessionRedis): OidcStore { }, set(sid, payload, callback) { const ttl = Math.ceil(payload.header.exp - Date.now() / 1000); + const stored = { + ...payload, + data: { + id_token: payload.data.id_token, + sessionExpiresAt: payload.data.sessionExpiresAt, + groups: payload.data.groups, + is_admin: payload.data.is_admin, + }, + }; const operation = - ttl > 0 ? client.set(key(sid), JSON.stringify(payload), { EX: ttl }) : client.del(key(sid)); + ttl > 0 ? client.set(key(sid), JSON.stringify(stored), { EX: ttl }) : client.del(key(sid)); void operation.then( () => callback?.(), (error: unknown) => callback?.(error), diff --git a/backend/src/auth/user.ts b/backend/src/auth/user.ts index ca0dc8d..831f72a 100644 --- a/backend/src/auth/user.ts +++ b/backend/src/auth/user.ts @@ -27,14 +27,9 @@ export function authenticatedUser(req: Request): User { return { sub: claims.sub, cid: claims.cid, - sid: claim("sid"), - given_name: claim("given_name"), - locale: claim("locale"), - picture: claim("picture"), name: claim("name"), nickname: claim("nickname"), - family_name: claim("family_name"), - jti: claim("jti"), + locale: claim("locale"), groups: Array.isArray(groups) ? groups.filter((group): group is string => typeof group === "string") : [], diff --git a/backend/src/generated/schema.ts b/backend/src/generated/schema.ts index f126ad7..f6fdb06 100644 --- a/backend/src/generated/schema.ts +++ b/backend/src/generated/schema.ts @@ -164,15 +164,10 @@ export type InputRule = { export type User = { __typename?: 'User'; - sid?: Maybe<Scalars['String']['output']>; sub?: Maybe<Scalars['String']['output']>; - given_name?: Maybe<Scalars['String']['output']>; - locale?: Maybe<Scalars['String']['output']>; - picture?: Maybe<Scalars['String']['output']>; name?: Maybe<Scalars['String']['output']>; nickname?: Maybe<Scalars['String']['output']>; - family_name?: Maybe<Scalars['String']['output']>; - jti?: Maybe<Scalars['String']['output']>; + locale?: Maybe<Scalars['String']['output']>; cid?: Maybe<Scalars['String']['output']>; groups?: Maybe<Array<Maybe<Scalars['String']['output']>>>; is_admin?: Maybe<Scalars['Boolean']['output']>; @@ -344,15 +339,10 @@ export type RuleResolvers<ContextType = Context, ParentType extends ResolversPar }; export type UserResolvers<ContextType = Context, ParentType extends ResolversParentTypes['User'] = ResolversParentTypes['User']> = { - sid?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>; sub?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>; - given_name?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>; - locale?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>; - picture?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>; name?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>; nickname?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>; - family_name?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>; - jti?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>; + locale?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>; cid?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>; groups?: Resolver<Maybe<Array<Maybe<ResolversTypes['String']>>>, ParentType, ContextType>; is_admin?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>; diff --git a/backend/src/models/user.ts b/backend/src/models/user.ts index 14e0896..e0f3de3 100644 --- a/backend/src/models/user.ts +++ b/backend/src/models/user.ts @@ -1,13 +1,8 @@ export interface UserInfo { - sid: string; sub: string; - given_name: string; - locale: string; - picture: string; name: string; nickname: string; - family_name: string; - jti: string; + locale: string; cid: string; } diff --git a/backend/src/schemas/v1/user.gql b/backend/src/schemas/v1/user.gql index 8a5bb59..c75fb4a 100644 --- a/backend/src/schemas/v1/user.gql +++ b/backend/src/schemas/v1/user.gql @@ -1,13 +1,8 @@ type User { - sid: String sub: String - given_name: String - locale: String - picture: String name: String nickname: String - family_name: String - jti: String + locale: String cid: String groups: [String] is_admin: Boolean From 0eaf96fd9cc7a41857b371ec535adf7a84b7dd59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 12:26:59 +0200 Subject: [PATCH 23/41] Remove redundant event and rule model modules --- backend/src/models/event.ts | 5 ----- backend/src/models/index.ts | 4 +--- backend/src/models/rule.ts | 2 -- backend/src/resolvers/rule.resolver.ts | 5 +++-- backend/src/services/event.service.ts | 4 +++- backend/src/services/rule.service.ts | 14 +++++++------- backend/src/tests/rule.test.ts | 5 ++--- 7 files changed, 16 insertions(+), 23 deletions(-) delete mode 100644 backend/src/models/event.ts delete mode 100644 backend/src/models/rule.ts diff --git a/backend/src/models/event.ts b/backend/src/models/event.ts deleted file mode 100644 index 435ca1a..0000000 --- a/backend/src/models/event.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { InputEvent } from "../generated/schema"; - -export interface Event extends InputEvent { - booked_by: string; -} diff --git a/backend/src/models/index.ts b/backend/src/models/index.ts index cf496ba..52789dd 100644 --- a/backend/src/models/index.ts +++ b/backend/src/models/index.ts @@ -1,6 +1,4 @@ -import { Event } from "./event"; -import { Rule } from "./rule"; import { User } from "./user"; import { Error } from "./error"; -export { Event, Rule, User, Error }; +export { User, Error }; diff --git a/backend/src/models/rule.ts b/backend/src/models/rule.ts deleted file mode 100644 index f4081e8..0000000 --- a/backend/src/models/rule.ts +++ /dev/null @@ -1,2 +0,0 @@ -export type { InputRule as Rule } from "../generated/schema"; -export type { rule as dbRule } from "@prisma/client"; diff --git a/backend/src/resolvers/rule.resolver.ts b/backend/src/resolvers/rule.resolver.ts index 23284da..f8dfb66 100644 --- a/backend/src/resolvers/rule.resolver.ts +++ b/backend/src/resolvers/rule.resolver.ts @@ -1,4 +1,5 @@ -import { Rule, User } from "../models"; +import { User } from "../models"; +import type { InputRule } from "../generated/schema"; import { createRule, deleteRule } from "../services/rule.service"; import { Tools } from "../utils/commonTypes"; import { ruleResult } from "./serialize"; @@ -18,7 +19,7 @@ export const getRuleQResolvers = ({ prisma }: Tools) => ({ }); export const getRuleMResolvers = ({ prisma }: Tools) => ({ - createRule: async (_: unknown, { rule }: { rule: Rule }, { user }: { user: User }) => { + createRule: async (_: unknown, { rule }: { rule: InputRule }, { user }: { user: User }) => { return createRule(prisma, rule, user); }, deleteRule: async (_: unknown, { id }: { id: string }, { user }: { user: User }) => { diff --git a/backend/src/services/event.service.ts b/backend/src/services/event.service.ts index 6e59b5c..f1fa1d7 100644 --- a/backend/src/services/event.service.ts +++ b/backend/src/services/event.service.ts @@ -1,9 +1,11 @@ import { Prisma, PrismaClient, event, room } from "@prisma/client"; import { Error, User } from "../models"; -import { Event } from "../models/event"; +import type { InputEvent } from "../generated/schema"; import { checkRules } from "./rule.service"; import { setTimeout as delay } from "node:timers/promises"; +type Event = InputEvent & { booked_by: string }; + /* * Events must end after they start */ diff --git a/backend/src/services/rule.service.ts b/backend/src/services/rule.service.ts index 0f6efd8..7c40a59 100644 --- a/backend/src/services/rule.service.ts +++ b/backend/src/services/rule.service.ts @@ -1,7 +1,7 @@ -import { Event, Rule, Error, User } from "../models"; +import { Error, User } from "../models"; +import type { InputEvent, InputRule } from "../generated/schema"; import { to } from "../utils"; import { Prisma, rule } from "@prisma/client"; -import { dbRule } from "../models/rule"; /** * A single rule that applies to a specific time slot @@ -107,13 +107,13 @@ export const mergeRules = (rules: ExplicitRule[]): ExplicitRule[] => { return mergedRules; }; -const breaksExplicitRule = (rule: ExplicitRule, event: Event): boolean => { +const breaksExplicitRule = (rule: ExplicitRule, event: InputEvent): boolean => { const start = new Date(event.start); const end = new Date(event.end); return rule.start < end && rule.end > start && !rule.allow; }; -export const doesObeyRules = (rules: rule[], event: Event): Error | null => { +export const doesObeyRules = (rules: rule[], event: InputEvent): Error | null => { const start = new Date(event.start); const end = new Date(event.end); @@ -151,7 +151,7 @@ export const getRulesBetween = async (prisma: Prisma.TransactionClient, from: Da }); }; -export const checkRules = async (prisma: Prisma.TransactionClient, event: Event) => { +export const checkRules = async (prisma: Prisma.TransactionClient, event: InputEvent) => { const rules = await prisma.rule.findMany({ where: { room: { @@ -173,7 +173,7 @@ const validDateTime = (start: Date, end: Date): boolean => { export const createRule = async ( prisma: Prisma.TransactionClient, - rule: Rule, + rule: InputRule, user: User, ): Promise<Error | null> => { if (!user.is_admin) { @@ -236,7 +236,7 @@ export const deleteRule = async ( en: "You do not have permission to delete rules", }; } - const rule: dbRule | null = await prisma.rule.findUnique({ + const rule = await prisma.rule.findUnique({ where: { id: id }, }); if (!rule) { diff --git a/backend/src/tests/rule.test.ts b/backend/src/tests/rule.test.ts index 184ef94..7a8a5e5 100644 --- a/backend/src/tests/rule.test.ts +++ b/backend/src/tests/rule.test.ts @@ -9,7 +9,7 @@ import { doesObeyRules, ruleDateBounds, } from "../services/rule.service"; -import type { Event } from "../models/event"; +import type { InputEvent } from "../generated/schema"; import { rule } from "@prisma/client"; const defaultRule: rule = { @@ -179,13 +179,12 @@ describe("Booking rule scope", () => { assert.equal(bounds.end_date.gte.toISOString(), "2026-10-25T00:00:00.000Z"); }); it("does not let permission in one room override restrictions in another", () => { - const booking: Event = { + const booking: InputEvent = { title: "Two rooms", start: "2021-08-20T11:00", end: "2021-08-20T12:00", room: ["BIG_HUB", "GROUP_ROOM"], phone: "0701234567", - booked_by: "member", booked_as: "digit", booking_terms: true, }; From bf771f497fc84a0c4f64dd56e78b1222ee8fc46d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 12:30:34 +0200 Subject: [PATCH 24/41] Remove E2E configuration-only unit tests --- e2e/application-images.test.ts | 67 ---------------------------------- vite.config.ts | 2 +- 2 files changed, 1 insertion(+), 68 deletions(-) delete mode 100644 e2e/application-images.test.ts diff --git a/e2e/application-images.test.ts b/e2e/application-images.test.ts deleted file mode 100644 index 0cbc1fb..0000000 --- a/e2e/application-images.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { describe, expect, it } from "vite-plus/test"; -import { applicationImages } from "./application-images"; - -const frontend = `ghcr.io/cthit/bookit-node-frontend@sha256:${"a".repeat(64)}`; -const backend = `ghcr.io/cthit/bookit-node-backend@sha256:${"b".repeat(64)}`; - -describe("E2E application mode", () => { - it("defaults to local development servers", () => { - expect(applicationImages({})).toBeUndefined(); - }); - - it("requires published-image mode in CI", () => { - expect(() => applicationImages({ CI: "true" })).toThrow("CI E2E requires"); - }); - - it("accepts immutable frontend and backend references", () => { - expect( - applicationImages({ - E2E_MODE: "images", - CI: "true", - BOOKIT_FRONTEND_IMAGE: frontend, - BOOKIT_BACKEND_IMAGE: backend, - }), - ).toEqual({ frontend, backend }); - }); - - it("accepts commit tags from CI", () => { - const commit = "c".repeat(40); - const images = { - frontend: `ghcr.io/cthit/bookit-node-frontend:${commit}`, - backend: `ghcr.io/cthit/bookit-node-backend:${commit}`, - }; - expect( - applicationImages({ - E2E_MODE: "images", - CI: "true", - BOOKIT_FRONTEND_IMAGE: images.frontend, - BOOKIT_BACKEND_IMAGE: images.backend, - }), - ).toEqual(images); - }); - - it.each([ - undefined, - "ghcr.io/cthit/bookit-node-backend:latest", - "ghcr.io/cthit/bookit-node-backend:abc1234", - "backend@sha256:abc", - ])("rejects missing or unpinned image references: %s", (value) => { - expect(() => - applicationImages({ - E2E_MODE: "images", - BOOKIT_FRONTEND_IMAGE: frontend, - BOOKIT_BACKEND_IMAGE: value, - }), - ).toThrow("BOOKIT_BACKEND_IMAGE must use"); - }); - - it("does not silently ignore image references in development mode", () => { - expect(() => applicationImages({ BOOKIT_FRONTEND_IMAGE: frontend })).toThrow( - "Set E2E_MODE=images", - ); - }); - - it("rejects unknown modes", () => { - expect(() => applicationImages({ E2E_MODE: "production" })).toThrow("E2E_MODE must be"); - }); -}); diff --git a/vite.config.ts b/vite.config.ts index c480ea2..ec6a26f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from "vite-plus"; export default defineConfig({ test: { - include: ["backend/src/**/*.test.ts", "frontend/src/**/*.test.ts", "e2e/**/*.test.ts"], + include: ["backend/src/**/*.test.ts", "frontend/src/**/*.test.ts"], environment: "node", }, lint: { From 3abf10f665e8484eee193db3cdea6192d46a881b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 12:38:37 +0200 Subject: [PATCH 25/41] Run E2E exclusively in containers through Make --- .github/workflows/ci.yml | 4 +- e2e/Makefile | 36 ++++++ e2e/README.md | 147 +++++------------------ e2e/application-images.ts | 36 ------ e2e/compose.ts | 238 +++++++++----------------------------- package.json | 2 +- 6 files changed, 127 insertions(+), 336 deletions(-) create mode 100644 e2e/Makefile delete mode 100644 e2e/application-images.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 03ede56..8b13885 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,9 +109,7 @@ jobs: contents: read packages: read env: - E2E_MODE: images - BOOKIT_FRONTEND_IMAGE: ghcr.io/cthit/bookit-node-frontend:${{ github.sha }} - BOOKIT_BACKEND_IMAGE: ghcr.io/cthit/bookit-node-backend:${{ github.sha }} + VERSION: ${{ github.sha }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: diff --git a/e2e/Makefile b/e2e/Makefile new file mode 100644 index 0000000..d4becf4 --- /dev/null +++ b/e2e/Makefile @@ -0,0 +1,36 @@ +export VERSION ?= +export BOOKIT_FRONTEND_IMAGE ?= +export BOOKIT_BACKEND_IMAGE ?= + +.PHONY: run-e2e +run-e2e: + @set -eu; \ + local_frontend=""; local_backend=""; \ + cleanup() { \ + if [ -n "$$local_frontend" ]; then docker image rm "$$local_frontend" || true; fi; \ + if [ -n "$$local_backend" ]; then docker image rm "$$local_backend" || true; fi; \ + }; \ + trap cleanup EXIT; trap 'exit 130' INT; trap 'exit 143' TERM; \ + if [ -n "$$VERSION" ]; then \ + if [ -n "$$BOOKIT_FRONTEND_IMAGE$$BOOKIT_BACKEND_IMAGE" ]; then \ + echo "Use VERSION or explicit image references, not both" >&2; exit 1; \ + fi; \ + BOOKIT_FRONTEND_IMAGE="ghcr.io/cthit/bookit-node-frontend:$$VERSION"; \ + BOOKIT_BACKEND_IMAGE="ghcr.io/cthit/bookit-node-backend:$$VERSION"; \ + fi; \ + if [ -n "$$BOOKIT_FRONTEND_IMAGE$$BOOKIT_BACKEND_IMAGE" ]; then \ + : "$${BOOKIT_FRONTEND_IMAGE:?Both image references are required}"; \ + : "$${BOOKIT_BACKEND_IMAGE:?Both image references are required}"; \ + docker pull --platform linux/amd64 "$$BOOKIT_FRONTEND_IMAGE"; \ + docker pull --platform linux/amd64 "$$BOOKIT_BACKEND_IMAGE"; \ + elif [ -n "$${CI:-}" ]; then \ + echo "CI requires VERSION or explicit image references" >&2; exit 1; \ + else \ + local_frontend="bookit-e2e-frontend:run-$$$$"; \ + local_backend="bookit-e2e-backend:run-$$$$"; \ + docker build --pull --platform linux/amd64 -f ../frontend/Dockerfile -t "$$local_frontend" ..; \ + docker build --pull --platform linux/amd64 -f ../backend/Dockerfile -t "$$local_backend" ..; \ + BOOKIT_FRONTEND_IMAGE="$$local_frontend"; BOOKIT_BACKEND_IMAGE="$$local_backend"; \ + fi; \ + export BOOKIT_FRONTEND_IMAGE BOOKIT_BACKEND_IMAGE; \ + pnpm --dir .. exec playwright test diff --git a/e2e/README.md b/e2e/README.md index 359da7e..11cf7d9 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -1,127 +1,44 @@ # BookIT browser tests -The suite exercises the real BookIT backend and frontend against a real Gamma -identity provider. `compose.ts` owns the complete environment: an isolated Docker -network, two PostgreSQL containers, two Redis containers, Gamma, Prisma schema -initialization, and either application processes or published application containers. Ports are assigned per run. It -does not read another checkout or use a developer's database or credentials. +Requires Docker, Make, Node and the repository's pnpm dependencies. Install the +browser once with `pnpm exec playwright install chromium`. -Install dependencies and Playwright Chromium, then use `pnpm test:e2e`. Docker -must be running. A worker shares the container environment, while each test gets -a fresh browser context and cleared BookIT bookings/rules. Run with one worker -to keep the suite's resource usage predictable. - -## Development servers or published images - -Local runs default to development servers (`E2E_MODE=dev`). -CI requires `E2E_MODE=images` and refuses to fall back to -development servers if either image is missing. - -To run the same suite locally against specific published images: +From the repository root: ```sh -# For private packages, authenticate Docker with read:packages access first. -docker login ghcr.io -E2E_MODE=images \ -BOOKIT_FRONTEND_IMAGE='ghcr.io/cthit/bookit-node-frontend:<commit-sha>' \ -BOOKIT_BACKEND_IMAGE='ghcr.io/cthit/bookit-node-backend:<commit-sha>' \ -pnpm test:e2e +make -C e2e run-e2e # Build and test the current checkout +make -C e2e run-e2e VERSION='<sha>' # Test published commit images +make -C e2e run-e2e VERSION=v1.2.0 # Test a release or any other published tag ``` -Use the full 40-character commit SHA. Image digests (`@sha256:<64 hex characters>`) -also work; tags such as `latest` are rejected. Images are pulled on each run. -Docker/Testcontainers use the -host's Docker credential store; credentials are not forwarded into containers. -Public images do not require a login. - -Image mode runs the backend image once with `prisma db push` against the -empty, isolated BookIT database, and checks its exit status before starting the -application. It does not generate a client from the checkout or migrate on normal -backend startup. No production database is touched. - -The browser connects to the backend's mapped port. Backend API routes stay under -`/api`; all other requests proxy to the Nginx frontend container on port 80, as -in production Compose. Both BookIT containers retain their production commands -and the backend runs with `NODE_ENV=production`. Images target `linux/amd64`; -Docker Desktop on Apple Silicon runs them under emulation. - -In image mode Gamma uses `gamma.localhost:<port>` as its single OIDC issuer: -Chromium resolves it to host loopback, while the backend resolves the identical -name through a Docker network alias. Gamma listens on that port inside its -container too. No issuer rewriting, authentication mocks, or development proxy -are involved. - -## CI image lifecycle and fork PRs - -Each branch push runs quality checks, publishes frontend/backend images to GHCR -as `:<commit-sha>`. E2E waits for publication, then pulls those commit tags directly. -Same-repository -PRs use that branch CI; fork PRs run quality checks separately. Publication has -package-write permission; E2E has package-read permission. - -Publishing a GitHub Release requires the latest CI run for its commit to pass, -then copies the commit images to the release tag (for example, `v1.2.0`). -No rebuild, main-merge promotion, or -`latest` update occurs. Release tags must be valid Docker tags, not `latest` or -commit SHAs. Prereleases use their exact release tag too. +`pnpm test:e2e` runs the same Make target. For private GHCR images, first use +`docker login ghcr.io` with read-only package access. -CI and releases share a per-commit concurrency group so publication and release -tagging cannot race. There is no image-metadata artifact to download. - -Fork PRs and Dependabot PRs run quality checks only. They receive no registry -credentials and skip publication/image E2E. After reviewing their code, -a maintainer must place it on a trusted branch in this repository to get image -coverage before merging. We do not use `pull_request_target` to execute PR code. -GHCR packages must grant this repository's Actions token write/read access; this -may need configuring for pre-existing packages. - -Commit tags are retained; apply a registry retention policy -separately without removing digests referenced by production tags. Failures retain -the E2E console log (including image references and startup logs), browser traces, -screenshots, video, and bounded per-container logs. Normal completion and failure -both stop the owned containers/network; Testcontainers' resource reaper handles -abrupt runner termination. - -The spec files contain focused, independent tests rather than one long test -per file. Booking lifecycle and concurrency, calendar navigation and drag/edit -consistency, language persistence, mobile layout, rule validation/lifecycle, and -UI/API permissions have separate results. Each test gets its own login and data -reset; the services stay shared for the worker. - -Prefer `getByRole` with an accessible name and scope it to the relevant dialog or -navigation when needed. Date/time fields use React Aria's named groups and -editable spinbutton segments. `date-time-helpers.ts` enters values through the -keyboard and asserts their accessible numeric values. These are the controls' -actual semantics, not artificial roles added to satisfy a selector. - -E2E source uses the same Vite+ formatter as the application: +To test specific digests instead of a shared tag: ```sh -pnpm exec vp fmt e2e playwright.config.ts +make -C e2e run-e2e \ + BOOKIT_FRONTEND_IMAGE='ghcr.io/cthit/bookit-node-frontend@sha256:<digest>' \ + BOOKIT_BACKEND_IMAGE='ghcr.io/cthit/bookit-node-backend@sha256:<digest>' ``` -Use `pnpm format` for the whole repository. `pnpm check` checks E2E formatting and -lint in CI as well. - -Tests import the extended `test` and `expect` from `./fixtures`. The default page -logs in through Gamma as `member` before the test starts. A spec can use -`test.use({ role: "admin" })` or `test.use({ role: "outsider" })`; `loginAs` lets a -test explicitly switch identity. `authenticate: false` is reserved for tests of -the unauthenticated flow. - -The synthetic admin has the BookIT client's `admin` authority, configured through -Gamma's UI. Admin and member belong to digIT; outsider belongs to no group. -Gamma's global bootstrap administrator is used only to provision the OAuth -client and its CLIENT API key. Authentication and authorization are never mocked. - -Image versions are explicit in `compose.ts`. BookIT uses PostgreSQL 12.22 and -Redis 5.0.14 to cover its intentionally deferred server upgrades. Gamma 2.5.1 is -pinned by digest, with PostgreSQL 16.0 and Redis 5.0.14 matching the working -`chalmers.it` `test/gamma-integration` reference. These pins are compatibility -baselines, not a claim that the old PostgreSQL/Redis releases receive security -fixes. Upgrade the BookIT servers in their separate infrastructure PRs and update -these test pins alongside them. - -On failure, Playwright retains browser artifacts and the fixture attaches bounded -application/Gamma logs with bootstrap credentials redacted. Startup failures print -those logs before cleaning up the owned environment. +Every run uses fresh Testcontainers-managed containers, volumes and a network: +BookIT frontend/backend, Gamma, and separate PostgreSQL/Redis instances for each +app. The backend image initializes the empty BookIT database before startup. +Requests go through the backend to the Nginx frontend, as in production. +No development servers or existing databases are used. + +Tests log in through Gamma with member/admin/outsider fixtures and reset bookings +and rules between tests. Completion or failure removes the owned containers, +volumes and network; Testcontainers' reaper handles interrupted runs. Locally built +image tags are removed on exit. Build caches and unrelated Docker resources are +left alone. Published images are pulled on every run. + +Failures retain Playwright traces, screenshots, video and service logs. +CI publishes commit images after quality checks and runs this same target with +`VERSION`. Fork/Dependabot PRs get quality checks only, without registry credentials; +image E2E requires a reviewed branch in this repository. GitHub releases retag +the tested commit images without rebuilding. + +Service versions are pinned in `compose.ts`. BookIT's PostgreSQL 12 and Redis 5 +upgrades remain separate follow-ups. diff --git a/e2e/application-images.ts b/e2e/application-images.ts deleted file mode 100644 index 391fc19..0000000 --- a/e2e/application-images.ts +++ /dev/null @@ -1,36 +0,0 @@ -export interface ApplicationImages { - frontend: string; - backend: string; -} - -export function applicationImages( - env: NodeJS.ProcessEnv = process.env, -): ApplicationImages | undefined { - const mode = env.E2E_MODE ?? "dev"; - if (mode !== "dev" && mode !== "images") { - throw new Error("E2E_MODE must be dev or images"); - } - if (env.CI && mode !== "images") { - throw new Error("CI E2E requires E2E_MODE=images"); - } - - if (mode === "dev") { - if (env.BOOKIT_FRONTEND_IMAGE || env.BOOKIT_BACKEND_IMAGE) { - throw new Error("Set E2E_MODE=images when supplying BookIT image references"); - } - return undefined; - } - - const image = (name: string): string => { - const value = env[name]; - if (!value || !/^[a-z0-9][a-z0-9._:/-]*(?::[a-f0-9]{40}|@sha256:[a-f0-9]{64})$/.test(value)) { - throw new Error(`${name} must use a full commit-SHA tag or an @sha256 image digest`); - } - return value; - }; - - return { - frontend: image("BOOKIT_FRONTEND_IMAGE"), - backend: image("BOOKIT_BACKEND_IMAGE"), - }; -} diff --git a/e2e/compose.ts b/e2e/compose.ts index a118eeb..47be3e9 100644 --- a/e2e/compose.ts +++ b/e2e/compose.ts @@ -1,19 +1,7 @@ import { expect, type Browser, type Page } from "@playwright/test"; import { PostgreSqlContainer, type StartedPostgreSqlContainer } from "@testcontainers/postgresql"; -import { - GenericContainer, - Network, - PullPolicy, - Wait, - type StartedTestContainer, -} from "testcontainers"; -import { spawn, type ChildProcess } from "node:child_process"; +import { GenericContainer, Network, Wait, type StartedTestContainer } from "testcontainers"; import { createServer, type AddressInfo } from "node:net"; -import { resolve } from "node:path"; -import { setTimeout as delay } from "node:timers/promises"; -import { applicationImages } from "./application-images"; - -const root = resolve(__dirname, ".."); export const images = { bookitPostgres: "postgres:12.22-alpine", @@ -91,21 +79,6 @@ async function unusedPort(): Promise<number> { }); } -async function stopProcess(child: ChildProcess): Promise<void> { - if (!child.pid || child.exitCode !== null || child.signalCode !== null) { - return; - } - - const exited = new Promise<void>((resolve) => child.once("exit", () => resolve())); - process.kill(-child.pid, "SIGTERM"); - await Promise.race([exited, delay(10_000)]); - - if (child.exitCode === null && child.signalCode === null) { - process.kill(-child.pid, "SIGKILL"); - await exited; - } -} - async function provisionClient(browser: Browser, gammaUrl: string, appUrl: string) { const context = await browser.newContext(); @@ -184,10 +157,13 @@ export async function loginAs( } export async function compose(browser: Browser): Promise<Environment> { - const appImages = applicationImages(); + const frontendImage = process.env.BOOKIT_FRONTEND_IMAGE; + const backendImage = process.env.BOOKIT_BACKEND_IMAGE; + if (!frontendImage || !backendImage) { + throw new Error("Run E2E with make -C e2e run-e2e"); + } const network = await new Network().start(); const containers: StartedTestContainer[] = []; - const processes: ChildProcess[] = []; const logs: Record<string, string> = {}; let stopped = false; @@ -211,17 +187,9 @@ export async function compose(browser: Browser): Promise<Environment> { stopped = true; const failures: unknown[] = []; - for (const child of processes.toReversed()) { - try { - await stopProcess(child); - } catch (error) { - failures.push(error); - } - } - for (const container of containers.toReversed()) { try { - await container.stop(); + await container.stop({ remove: true, removeVolumes: true }); } catch (error) { failures.push(error); } @@ -238,27 +206,6 @@ export async function compose(browser: Browser): Promise<Environment> { } }; - const startProcess = (name: string, args: string[], env: Record<string, string>) => { - const child = spawn("pnpm", args, { - cwd: root, - env: { ...process.env, ...env }, - detached: true, - stdio: ["ignore", "pipe", "pipe"], - }); - - processes.push(child); - child.stdout?.on("data", (chunk: Buffer) => record(name, chunk)); - child.stderr?.on("data", (chunk: Buffer) => record(name, chunk)); - child.on("error", (error) => record(name, error.message)); - child.on("exit", (code) => { - if (code !== null && code !== 0 && !stopped) { - console.error(`[${name}] exited with code ${code}\n${logs[name] ?? ""}`); - } - }); - - return child; - }; - try { console.log("Starting isolated PostgreSQL, Redis and Gamma containers..."); const gammaDb = await track( @@ -289,23 +236,19 @@ export async function compose(browser: Browser): Promise<Environment> { .start(), ); - const bookitRedis = await track( + await track( new GenericContainer(images.bookitRedis) .withNetwork(network) .withNetworkAliases("bookit-redis") - .withExposedPorts(6379) .withWaitStrategy(Wait.forLogMessage("Ready to accept connections")) .start(), ); const gammaPort = await unusedPort(); // One issuer URL: browser loopback, Docker network alias. - const gammaHost = appImages ? "gamma.localhost" : "localhost"; - const gammaUrl = `http://${gammaHost}:${gammaPort}`; - const gammaContainerPort = appImages ? gammaPort : 8081; + const gammaUrl = `http://gamma.localhost:${gammaPort}`; const appPort = await unusedPort(); const appUrl = `http://localhost:${appPort}`; - const frontendPort = await unusedPort(); await track( new GenericContainer(images.gamma) @@ -319,7 +262,7 @@ export async function compose(browser: Browser): Promise<Environment> { DB_USER: gammaDb.getUsername(), DB_PASSWORD: gammaDb.getPassword(), REDIS_HOST: "gamma-redis", - SERVER_PORT: String(gammaContainerPort), + SERVER_PORT: String(gammaPort), BASE_URL: gammaUrl, PRODUCTION: "false", IS_MOCKING: "true", @@ -330,11 +273,11 @@ export async function compose(browser: Browser): Promise<Environment> { .withCopyContentToContainer([ { content: JSON.stringify(seed), target: "/tmp/bookit-gamma-seed.json" }, ]) - .withExposedPorts({ container: gammaContainerPort, host: gammaPort }) + .withExposedPorts({ container: gammaPort, host: gammaPort }) .withLogConsumer((stream) => stream.on("data", (chunk: Buffer) => record("gamma", chunk))) .withWaitStrategy( Wait.forAll([ - Wait.forHttp("/login", gammaContainerPort).forStatusCode(200), + Wait.forHttp("/login", gammaPort).forStatusCode(200), Wait.forLogMessage(/Api key of type INFO has been generated/), ]), ) @@ -346,131 +289,64 @@ export async function compose(browser: Browser): Promise<Environment> { const client = await provisionClient(browser, gammaUrl, appUrl); const env = { - NODE_ENV: appImages ? "production" : "test", + NODE_ENV: "production", TZ: "Europe/Stockholm", - DATABASE_URL: appImages - ? "postgresql://bookit_test:bookit_test@bookit-db:5432/bookit_test" - : bookitDb.getConnectionUri(), - REDIS_HOST: appImages ? "bookit-redis" : bookitRedis.getHost(), - REDIS_PORT: appImages ? "6379" : String(bookitRedis.getMappedPort(6379)), + DATABASE_URL: "postgresql://bookit_test:bookit_test@bookit-db:5432/bookit_test", + REDIS_HOST: "bookit-redis", + REDIS_PORT: "6379", REDIS_PASS: "", SESSION_SECRET: "bookit-isolated-e2e-session-secret", SECRET: "bookit-isolated-e2e-oidc-secret-at-least-32-characters", BASE_URL: appUrl, BACKEND_URL: appUrl, - PORT: appImages ? "8080" : String(appPort), - FRONTEND_URL: appImages ? "http://bookit-frontend:80" : `http://127.0.0.1:${frontendPort}`, + PORT: "8080", + FRONTEND_URL: "http://bookit-frontend:80", ISSUER_BASE_URL: gammaUrl, CLIENT_ID: client.clientId, CLIENT_SECRET: client.clientSecret, API_KEY: client.apiKey, }; - if (appImages) { - record("images", JSON.stringify(appImages)); - console.log(`Using published BookIT images: ${JSON.stringify(appImages)}`); - console.log("Initializing the isolated database with the published backend image..."); - await track( - new GenericContainer(appImages.backend) - .withPullPolicy(PullPolicy.alwaysPull()) - .withPlatform("linux/amd64") - .withNetwork(network) - .withEnvironment({ DATABASE_URL: env.DATABASE_URL }) - .withCommand(["./node_modules/.bin/prisma", "db", "push"]) - .withLogConsumer((stream) => - stream.on("data", (chunk: Buffer) => record("schema", chunk)), - ) - .withWaitStrategy(Wait.forOneShotStartup()) - .withStartupTimeout(120_000) - .start(), - ); - await track( - new GenericContainer(appImages.frontend) - .withPullPolicy(PullPolicy.alwaysPull()) - .withPlatform("linux/amd64") - .withNetwork(network) - .withNetworkAliases("bookit-frontend") - .withExposedPorts(80) - .withLogConsumer((stream) => - stream.on("data", (chunk: Buffer) => record("frontend", chunk)), - ) - .withWaitStrategy(Wait.forHttp("/", 80).forStatusCode(200)) - .withStartupTimeout(120_000) - .start(), - ); - await track( - new GenericContainer(appImages.backend) - .withPullPolicy(PullPolicy.alwaysPull()) - .withPlatform("linux/amd64") - .withNetwork(network) - .withEnvironment(env) - .withExposedPorts({ container: 8080, host: appPort }) - .withLogConsumer((stream) => - stream.on("data", (chunk: Buffer) => record("backend", chunk)), - ) - .withWaitStrategy(Wait.forHttp("/api/health", 8080).forStatusCode(200)) - .withStartupTimeout(120_000) - .start(), - ); - } else { - console.log("Generating Prisma client and initializing the isolated BookIT schema..."); - for (const command of [["generate"], ["db", "push"]]) { - const migrate = startProcess( - "schema", - ["--dir", "backend", "exec", "prisma", ...command], - env, - ); - await new Promise<void>((resolve, reject) => { - migrate.once("error", reject); - migrate.once("exit", (code) => - code === 0 - ? resolve() - : reject(new Error(`BookIT schema setup failed: ${logs.schema ?? ""}`)), - ); - }); - } - - const frontend = startProcess( - "frontend", - [ - "--dir", - "frontend", - "dev", - "--host", - "127.0.0.1", - "--port", - String(frontendPort), - "--strictPort", - ], - env, - ); - const backend = startProcess("backend", ["--dir", "backend", "start"], env); - - await expect - .poll( - async () => { - for (const child of [frontend, backend]) { - if (child.exitCode !== null || child.signalCode !== null) { - throw new Error( - `BookIT exited during startup.\n${logs.backend ?? ""}\n${logs.frontend ?? ""}`, - ); - } - } - const front = await fetch(env.FRONTEND_URL, { - signal: AbortSignal.timeout(2_000), - }).catch(() => undefined); - await front?.body?.cancel(); - const back = await fetch(`${appUrl}/api/health`, { - redirect: "manual", - signal: AbortSignal.timeout(2_000), - }).catch(() => undefined); - await back?.body?.cancel(); - return front?.status === 200 && back?.status === 200; - }, - { timeout: 120_000, intervals: [500, 1000] }, + record("images", JSON.stringify({ frontend: frontendImage, backend: backendImage })); + console.log( + `Using BookIT images: ${JSON.stringify({ frontend: frontendImage, backend: backendImage })}`, + ); + console.log("Initializing the isolated database with the backend image..."); + await track( + new GenericContainer(backendImage) + .withPlatform("linux/amd64") + .withNetwork(network) + .withEnvironment({ DATABASE_URL: env.DATABASE_URL }) + .withCommand(["./node_modules/.bin/prisma", "db", "push"]) + .withLogConsumer((stream) => stream.on("data", (chunk: Buffer) => record("schema", chunk))) + .withWaitStrategy(Wait.forOneShotStartup()) + .withStartupTimeout(120_000) + .start(), + ); + await track( + new GenericContainer(frontendImage) + .withPlatform("linux/amd64") + .withNetwork(network) + .withNetworkAliases("bookit-frontend") + .withExposedPorts(80) + .withLogConsumer((stream) => + stream.on("data", (chunk: Buffer) => record("frontend", chunk)), ) - .toBe(true); - } + .withWaitStrategy(Wait.forHttp("/", 80).forStatusCode(200)) + .withStartupTimeout(120_000) + .start(), + ); + await track( + new GenericContainer(backendImage) + .withPlatform("linux/amd64") + .withNetwork(network) + .withEnvironment(env) + .withExposedPorts({ container: 8080, host: appPort }) + .withLogConsumer((stream) => stream.on("data", (chunk: Buffer) => record("backend", chunk))) + .withWaitStrategy(Wait.forHttp("/api/health", 8080).forStatusCode(200)) + .withStartupTimeout(120_000) + .start(), + ); return { appUrl, diff --git a/package.json b/package.json index 5f78c18..d5830f4 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "format": "vp fmt", "format:check": "vp fmt --check", "typecheck": "pnpm --dir backend typecheck && pnpm --dir frontend typecheck && tsc --noEmit -p e2e/tsconfig.json", - "test:e2e": "playwright test" + "test:e2e": "make -C e2e run-e2e" }, "devDependencies": { "@graphql-codegen/core": "^6.2.0", From 961776243017001266e741e57af416757273b238 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 12:51:23 +0200 Subject: [PATCH 26/41] Exercise E2E flows exclusively through the browser UI --- e2e/README.md | 4 +- e2e/booking-helpers.ts | 40 +------------ e2e/booking-lifecycle.spec.ts | 107 +++++++++++++--------------------- e2e/calendar-rules.spec.ts | 64 ++++++++++++-------- e2e/calendar.spec.ts | 65 +++++++++------------ e2e/permissions.spec.ts | 90 ++-------------------------- 6 files changed, 117 insertions(+), 253 deletions(-) diff --git a/e2e/README.md b/e2e/README.md index 11cf7d9..df55cc3 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -29,7 +29,9 @@ Requests go through the backend to the Nginx frontend, as in production. No development servers or existing databases are used. Tests log in through Gamma with member/admin/outsider fixtures and reset bookings -and rules between tests. Completion or failure removes the owned containers, +and rules between tests. Test data is created through the UI, and assertions check +what users see; tests do not call the API directly. +Completion or failure removes the owned containers, volumes and network; Testcontainers' reaper handles interrupted runs. Locally built image tags are removed on exit. Build caches and unrelated Docker resources are left alone. Published images are pulled on every run. diff --git a/e2e/booking-helpers.ts b/e2e/booking-helpers.ts index b8169c5..7163e8f 100644 --- a/e2e/booking-helpers.ts +++ b/e2e/booking-helpers.ts @@ -46,7 +46,7 @@ export async function fillBooking(page: Page, title: string) { } export async function createBooking(page: Page, title: string) { - await page.goto("/new-event"); + await page.getByRole("link", { name: "New booking", exact: true }).click(); await fillBooking(page, title); await page.getByRole("button", { name: "Save booking", exact: true }).click(); @@ -55,44 +55,8 @@ export async function createBooking(page: Page, title: string) { await expect(page.getByRole("button").filter({ hasText: title }).first()).toBeVisible(); } -export async function openBooking(page: Page, title: string): Promise<string> { +export async function openBooking(page: Page, title: string) { await page.getByRole("button").filter({ hasText: title }).first().click(); await expect(page.getByRole("dialog", { name: "Booking details", exact: true })).toBeVisible(); await expect(page.getByRole("heading", { name: title, exact: true })).toBeVisible(); - - const result = await graphql<{ events: { id: string; title: string }[] }>( - page, - "{ events { id title } }", - ); - - const id = result.events.find((event) => event.title === title)?.id; - if (!id) { - throw new Error("Booking details did not include a booking id"); - } - - await page.goto(`/bookings/${id}`); - await expect(page.getByRole("heading", { name: title, exact: true })).toBeVisible(); - - return id; -} - -export async function graphql<T>( - page: Page, - query: string, - variables: Record<string, unknown> = {}, -): Promise<T> { - const response = await page.request.post("/api/graphql/v1", { - headers: { Origin: new URL(page.url()).origin }, - data: { query, variables }, - }); - - expect(response.status()).toBe(200); - - const body = (await response.json()) as { data?: T; errors?: unknown[] }; - expect(body.errors).toBeUndefined(); - if (!body.data) { - throw new Error("GraphQL response has no data"); - } - - return body.data; } diff --git a/e2e/booking-lifecycle.spec.ts b/e2e/booking-lifecycle.spec.ts index c7d5014..d27faf8 100644 --- a/e2e/booking-lifecycle.spec.ts +++ b/e2e/booking-lifecycle.spec.ts @@ -1,27 +1,18 @@ import { test, expect } from "./fixtures"; -import { - acceptConditions, - bookingDate, - createBooking, - graphql, - openBooking, -} from "./booking-helpers"; -import type { - CreateBookingMutation, - DeleteBookingMutation, -} from "../frontend/src/generated/graphql"; +import { acceptConditions, createBooking, fillBooking, openBooking } from "./booking-helpers"; test("a group member creates, edits and deletes a persisted booking", async ({ page }) => { const title = "E2E digIT planning"; await createBooking(page, title); - const id = await openBooking(page, title); + await openBooking(page, title); await expect( page.getByText("Planning together in our shared room.", { exact: true }), ).toBeVisible(); await page.reload(); + await openBooking(page, title); await expect(page.getByRole("heading", { name: title, exact: true })).toBeVisible(); await page.getByRole("button", { name: "Edit", exact: true }).click(); @@ -37,7 +28,6 @@ test("a group member creates, edits and deletes a persisted booking", async ({ p await expect(page).toHaveURL(/\/$/); await openBooking(page, `${title} updated`); - await expect(page).toHaveURL((url) => url.pathname === `/bookings/${id}`); await expect(page.getByText("Agenda updated by a group member.", { exact: true })).toBeVisible(); await page.getByRole("button", { name: "Delete", exact: true }).click(); @@ -50,61 +40,46 @@ test("a group member creates, edits and deletes a persisted booking", async ({ p await expect(page).toHaveURL(/\/$/); await expect(page.getByRole("button").filter({ hasText: title })).toHaveCount(0); - await page.goto(`/bookings/${id}`); - await expect(page.getByRole("alert")).toHaveText("Booking not found."); + await page.reload(); + await createBooking(page, "E2E replacement booking"); + await expect(page.getByRole("button").filter({ hasText: title })).toHaveCount(0); }); -test("simultaneous requests cannot reserve the same room and time twice", async ({ page }) => { - const date = await bookingDate(page); - const titles = ["E2E concurrent booking A", "E2E concurrent booking B"]; - - const outcomes = await Promise.all( - titles.map((concurrentTitle) => - graphql<CreateBookingMutation>( - page, - "mutation($event: InputEvent!) { createEvent(event: $event) { en sv } }", - { - event: { - title: concurrentTitle, - phone: "0701234567", - booked_as: "digit", - booking_terms: true, - start: `${date}T15:00:00.000Z`, - end: `${date}T16:00:00.000Z`, - room: ["CTC"], - }, - }, +test("two booking forms cannot reserve the same room and time twice", async ({ page }) => { + const otherPage = await page.context().newPage(); + const bookings = [ + { page, title: "E2E concurrent booking A" }, + { page: otherPage, title: "E2E concurrent booking B" }, + ]; + + try { + await otherPage.goto("/"); + for (const { page: bookingPage, title } of bookings) { + await bookingPage.getByRole("link", { name: "New booking", exact: true }).click(); + await fillBooking(bookingPage, title); + } + + await Promise.all( + bookings.map(({ page: bookingPage }) => + bookingPage.getByRole("button", { name: "Save booking", exact: true }).click(), ), - ), - ); - - expect(outcomes.filter((outcome) => outcome.createEvent === null)).toHaveLength(1); - expect( - outcomes.filter((outcome) => outcome.createEvent?.en === "The time slot is already taken"), - ).toHaveLength(1); - - const persisted = await graphql<{ events: { id: string; title: string }[] }>( - page, - "{ events { id title } }", - ); - const concurrentBookings = persisted.events.filter((booking) => titles.includes(booking.title)); - expect(concurrentBookings).toHaveLength(1); - - const winner = concurrentBookings[0]; - if (!winner) { - throw new Error("The successful concurrent booking was not persisted"); + ); + + await expect + .poll(() => bookings.filter(({ page }) => new URL(page.url()).pathname === "/").length) + .toBe(1); + + const winner = bookings.find(({ page }) => new URL(page.url()).pathname === "/"); + const loser = bookings.find(({ page }) => new URL(page.url()).pathname !== "/"); + if (!winner || !loser) { + throw new Error("Expected one saved booking and one rejected booking form"); + } + + await expect(loser.page.getByRole("alert")).toHaveText("The time slot is already taken"); + await winner.page.reload(); + await expect(winner.page.getByRole("button").filter({ hasText: winner.title })).toHaveCount(1); + await expect(winner.page.getByRole("button").filter({ hasText: loser.title })).toHaveCount(0); + } finally { + await otherPage.close(); } - - const removed = await graphql<DeleteBookingMutation>( - page, - "mutation($id: String!) { deleteEvent(id: $id) { en sv } }", - { id: winner.id }, - ); - expect(removed.deleteEvent).toBeNull(); - - const afterCleanup = await graphql<{ events: { id: string; title: string }[] }>( - page, - "{ events { id title } }", - ); - expect(afterCleanup.events.filter((booking) => titles.includes(booking.title))).toHaveLength(0); }); diff --git a/e2e/calendar-rules.spec.ts b/e2e/calendar-rules.spec.ts index 8eb9224..979edad 100644 --- a/e2e/calendar-rules.spec.ts +++ b/e2e/calendar-rules.spec.ts @@ -1,5 +1,6 @@ import { test, expect } from "./fixtures"; -import { bookingDate, graphql } from "./booking-helpers"; +import { bookingDate } from "./booking-helpers"; +import { dateSegments, fillSegments } from "./date-time-helpers"; test.use({ role: "admin" }); @@ -8,33 +9,46 @@ test("overlapping room restrictions share a compact label and respect room filte }) => { const date = await bookingDate(page); - for (const [title, room] of [ - ["Maintenance", ["BIG_HUB", "GROUP_ROOM"]], - ["Reading", ["CTC"]], + await page.getByRole("navigation").getByRole("link", { name: "Rules", exact: true }).click(); + + for (const { title, rooms } of [ + { title: "Maintenance", rooms: ["Storhubben", "Grupprummet"] }, + { title: "Reading", rooms: ["CTC"] }, ]) { - const result = await graphql<{ createRule: { en: string } | null }>( - page, - "mutation($rule: InputRule!) { createRule(rule: $rule) { en } }", - { - rule: { - title, - description: "The rooms are unavailable during this time.", - room, - start_date: date, - end_date: date, - start_time: "12:00", - end_time: "13:00", - day_mask: 127, - priority: 10, - allow: false, - }, - }, - ); - - expect(result.createRule).toBeNull(); + await page.getByRole("button", { name: "New rule", exact: true }).click(); + const form = page.getByRole("dialog", { name: "New rule", exact: true }); + + await form.getByRole("textbox", { name: "Title", exact: true }).fill(title); + await form + .getByRole("textbox", { name: "Description", exact: true }) + .fill("The rooms are unavailable during this time."); + await form.getByRole("combobox", { name: "Availability", exact: true }).selectOption("false"); + + for (const name of ["Start date", "End date"]) { + await fillSegments(form.getByRole("group", { name, exact: true }), dateSegments(date)); + } + await fillSegments(form.getByRole("group", { name: "Start time", exact: true }), { + hour: 12, + minute: 0, + }); + await fillSegments(form.getByRole("group", { name: "End time", exact: true }), { + hour: 13, + minute: 0, + }); + + for (const room of rooms) { + await form.getByRole("checkbox", { name: room, exact: true }).check(); + } + for (const weekday of ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]) { + await form.getByRole("checkbox", { name: weekday, exact: true }).check(); + } + + await form.getByRole("button", { name: "Save rule", exact: true }).click(); + await expect(form).not.toBeVisible(); + await expect(page.getByRole("row").filter({ hasText: title })).toBeVisible(); } - await page.reload(); + await page.getByRole("navigation").getByRole("link", { name: "Calendar", exact: true }).click(); const label = page.getByRole("button", { name: "Maintenance · Reading", exact: true }); await expect(label).toHaveCount(1); diff --git a/e2e/calendar.spec.ts b/e2e/calendar.spec.ts index 26b4211..cf290d7 100644 --- a/e2e/calendar.spec.ts +++ b/e2e/calendar.spec.ts @@ -1,5 +1,5 @@ import { test, expect } from "./fixtures"; -import { createBooking, graphql } from "./booking-helpers"; +import { createBooking, openBooking } from "./booking-helpers"; import { expectSegments } from "./date-time-helpers"; test("room filters and period navigation show the expected bookings", async ({ page }) => { @@ -36,24 +36,12 @@ test("editing immediately after a calendar drag preserves the moved times", asyn const event = page.getByRole("button").filter({ hasText: title }); - // Prime the detail cache before moving the booking. - const readBooking = async () => { - const { events } = await graphql<{ - events: { title: string; start: string; end: string }[]; - }>(page, "{ events { title start end } }"); + const details = page.getByRole("dialog", { name: "Booking details", exact: true }); + const beginsAt = details.getByText("Begins at", { exact: true }).locator("..").locator("dd"); + const endsAt = details.getByText("Ends at", { exact: true }).locator("..").locator("dd"); - const booking = events.find((entry) => entry.title === title); - if (!booking) { - throw new Error("Calendar booking is missing"); - } - - return booking; - }; - - const originalBooking = await readBooking(); - await event.first().click(); - await expect(page.getByRole("dialog", { name: "Booking details", exact: true })).toBeVisible(); - await expect(page.getByRole("heading", { name: title, exact: true })).toBeVisible(); + await openBooking(page, title); + const originalStart = await beginsAt.innerText(); await page.keyboard.press("Escape"); await event.first().scrollIntoViewIfNeeded(); @@ -62,37 +50,32 @@ test("editing immediately after a calendar drag preserves the moved times", asyn throw new Error("Calendar booking has no drag target"); } - const movedResponse = page.waitForResponse( - (response) => - response.url().endsWith("/api/graphql/v1") && - Boolean(response.request().postData()?.includes("mutation UpdateBooking")), - ); - await page.mouse.move(box.x + box.width / 2, box.y + 10); await page.mouse.down(); await page.mouse.move(box.x + box.width / 2, box.y + box.height + 10, { steps: 20 }); await page.mouse.up(); - expect((await (await movedResponse).json()).data.editEvent).toBeNull(); + await expect(page.getByText("Booking moved", { exact: true })).toBeVisible(); - const movedBooking = await readBooking(); - expect(movedBooking.start).not.toBe(originalBooking.start); + await openBooking(page, title); + await expect(beginsAt).not.toHaveText(originalStart); + const movedStart = await beginsAt.innerText(); + const movedEnd = await endsAt.innerText(); - await event.first().click(); await page.getByRole("button", { name: "Edit", exact: true }).click(); - const movedLocalTime = await page.evaluate((value) => { - const date = new Date(value); - - return { + for (const [name, value] of [ + ["Begins at", movedStart], + ["Ends at", movedEnd], + ] as const) { + const date = new Date(value.replace(" ", "T")); + await expectSegments(page.getByRole("group", { name, exact: true }), { year: date.getFullYear(), month: date.getMonth() + 1, day: date.getDate(), hour: date.getHours(), minute: date.getMinutes(), - }; - }, movedBooking.start); - - await expectSegments(page.getByRole("group", { name: "Begins at", exact: true }), movedLocalTime); + }); + } await page .getByRole("textbox", { name: "Description", exact: true }) @@ -100,9 +83,13 @@ test("editing immediately after a calendar drag preserves the moved times", asyn await page.getByRole("button", { name: "Save booking", exact: true }).click(); await expect(page).toHaveURL(/\/$/); - const savedBooking = await readBooking(); - expect(savedBooking.start).toBe(movedBooking.start); - expect(savedBooking.end).toBe(movedBooking.end); + await page.reload(); + await openBooking(page, title); + await expect(beginsAt).toHaveText(movedStart); + await expect(endsAt).toHaveText(movedEnd); + await expect( + details.getByText("Updated after moving the booking.", { exact: true }), + ).toBeVisible(); }); test("the selected language survives a reload", async ({ page }) => { diff --git a/e2e/permissions.spec.ts b/e2e/permissions.spec.ts index 1c86bac..3b0286f 100644 --- a/e2e/permissions.spec.ts +++ b/e2e/permissions.spec.ts @@ -1,108 +1,30 @@ import { test, expect, loginAs } from "./fixtures"; -import { bookingDate, createBooking, graphql, openBooking } from "./booking-helpers"; -import type { - CurrentUserQuery, - DeleteBookingMutation, - CreateBookingMutation, - CreateRuleMutation, - BookingDetailQuery, -} from "../frontend/src/generated/graphql"; +import { createBooking, openBooking } from "./booking-helpers"; test("an outsider can view bookings but cannot access editing or administration", async ({ page, environment, }) => { await createBooking(page, "E2E protected group booking"); - const id = await openBooking(page, "E2E protected group booking"); await loginAs(page, environment, "outsider"); - const identity = await graphql<CurrentUserQuery>(page, "{ user { sub cid groups is_admin } }"); + await expect(page.getByText("BookIT Guest", { exact: true })).toBeVisible(); - expect(identity.user?.cid).toBe("bookguest"); - expect(identity.user?.groups).toEqual([]); - expect(identity.user?.is_admin).toBe(false); - - await page.goto("/new-event"); + await page.getByRole("link", { name: "New booking", exact: true }).click(); await expect(page.getByRole("alert")).toHaveText( "You need membership in an active group to make a booking.", ); await expect(page.getByRole("button", { name: "Save booking", exact: true })).toHaveCount(0); - await page.goto("/rules"); + await page.getByRole("navigation").getByRole("link", { name: "Rules", exact: true }).click(); await expect(page.getByRole("heading", { name: "Rules", exact: true })).toBeVisible(); await expect(page.getByRole("button", { name: "New rule", exact: true })).toHaveCount(0); - await page.goto(`/bookings/${id}`); + await page.getByRole("navigation").getByRole("link", { name: "Calendar", exact: true }).click(); + await openBooking(page, "E2E protected group booking"); await expect( page.getByRole("heading", { name: "E2E protected group booking", exact: true }), ).toBeVisible(); await expect(page.getByRole("button", { name: "Edit", exact: true })).toHaveCount(0); await expect(page.getByRole("button", { name: "Delete", exact: true })).toHaveCount(0); }); - -test("the API rejects unauthorized deletion, group booking and rule creation", async ({ - page, - environment, -}) => { - const title = "E2E protected group booking"; - - await createBooking(page, title); - const id = await openBooking(page, title); - - await loginAs(page, environment, "outsider"); - - const deletion = await graphql<DeleteBookingMutation>( - page, - "mutation($id: String!) { deleteEvent(id: $id) { en sv } }", - { id }, - ); - - expect(deletion.deleteEvent?.en).toBe("You may not delete this event"); - - const date = await bookingDate(page); - const forged = await graphql<CreateBookingMutation>( - page, - "mutation($event: InputEvent!) { createEvent(event: $event) { en sv } }", - { - event: { - title: "Forged booking", - phone: "0701234567", - start: `${date}T15:00:00.000Z`, - end: `${date}T16:00:00.000Z`, - room: ["CTC"], - booked_as: "digit", - booking_terms: true, - }, - }, - ); - - expect(forged.createEvent?.en).toBe("Booking group not specified"); - - const rule = await graphql<CreateRuleMutation>( - page, - "mutation($rule: InputRule!) { createRule(rule: $rule) { en sv } }", - { - rule: { - title: "Forged rule", - description: "", - start_date: date, - end_date: date, - start_time: "12:00", - end_time: "13:00", - day_mask: 127, - room: ["BIG_HUB"], - allow: false, - }, - }, - ); - - expect(rule.createRule?.en).toBe("You do not have permission to create rules"); - - const unchanged = await graphql<BookingDetailQuery>( - page, - "query($id: String!) { event(id: $id) { id title } }", - { id }, - ); - - expect(unchanged.event?.title).toBe("E2E protected group booking"); -}); From fa2262f542375d2d9c03d57509c83cf1b34325f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 13:06:09 +0200 Subject: [PATCH 27/41] Remove frontend API client unit tests --- frontend/src/api/client.test.ts | 32 -------------------------------- 1 file changed, 32 deletions(-) delete mode 100644 frontend/src/api/client.test.ts diff --git a/frontend/src/api/client.test.ts b/frontend/src/api/client.test.ts deleted file mode 100644 index fc709d5..0000000 --- a/frontend/src/api/client.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { afterEach, describe, expect, it, vi } from "vite-plus/test"; -import { CurrentUserDocument } from "../generated/graphql"; -import { checkMutation, request } from "./client"; - -afterEach(() => vi.unstubAllGlobals()); - -describe("GraphQL transport", () => { - it("sends credentials and rejects GraphQL failures even on HTTP 200", async () => { - const fetch = vi.fn().mockResolvedValue(Response.json({ errors: [{ message: "Denied" }] })); - vi.stubGlobal("fetch", fetch); - await expect(request(CurrentUserDocument, {})).rejects.toThrow("Denied"); - expect(fetch).toHaveBeenCalledWith( - "/api/graphql/v1", - expect.objectContaining({ - credentials: "same-origin", - method: "POST", - }), - ); - }); - it("reports expired authentication", async () => { - vi.stubGlobal("fetch", vi.fn().mockResolvedValue(new Response(null, { status: 401 }))); - await expect(request(CurrentUserDocument, {})).rejects.toThrow("session expired"); - }); - it("rejects an empty response rather than rendering false success", async () => { - vi.stubGlobal("fetch", vi.fn().mockResolvedValue(Response.json({}))); - await expect(request(CurrentUserDocument, {})).rejects.toThrow("no data"); - }); - it("preserves localized mutation validation errors", () => { - expect(() => checkMutation({ en: "Denied", sv: "Nekad" }, "sv")).toThrow("Nekad"); - expect(() => checkMutation(null, "en")).not.toThrow(); - }); -}); From 2a9d5e3c4f8f81175bc7d23a6ab18cc9c7c0899c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 13:13:13 +0200 Subject: [PATCH 28/41] Confirm calendar drag moves before saving bookings --- e2e/calendar.spec.ts | 146 ++++++++++++++++++++++++++++---- frontend/src/pages/calendar.tsx | 144 +++++++++++++++++++++++++------ 2 files changed, 246 insertions(+), 44 deletions(-) diff --git a/e2e/calendar.spec.ts b/e2e/calendar.spec.ts index cf290d7..cee5c8c 100644 --- a/e2e/calendar.spec.ts +++ b/e2e/calendar.spec.ts @@ -1,6 +1,21 @@ import { test, expect } from "./fixtures"; -import { createBooking, openBooking } from "./booking-helpers"; -import { expectSegments } from "./date-time-helpers"; +import { createBooking, fillBooking, openBooking } from "./booking-helpers"; +import { expectSegments, fillSegments } from "./date-time-helpers"; +import type { Page } from "@playwright/test"; + +async function dragBooking(page: Page, title: string) { + const event = page.getByRole("button").filter({ hasText: title }).first(); + await event.scrollIntoViewIfNeeded(); + const box = await event.boundingBox(); + if (!box) { + throw new Error("Calendar booking has no drag target"); + } + + await page.mouse.move(box.x + box.width / 2, box.y + 10); + await page.mouse.down(); + await page.mouse.move(box.x + box.width / 2, box.y + box.height + 10, { steps: 20 }); + await page.mouse.up(); +} test("room filters and period navigation show the expected bookings", async ({ page }) => { const title = "E2E calendar meeting"; @@ -29,37 +44,96 @@ test("room filters and period navigation show the expected bookings", async ({ p await expect(event.first()).toBeVisible(); }); -test("editing immediately after a calendar drag preserves the moved times", async ({ page }) => { +test("cancelling or dismissing a dragged booking preserves its original times", async ({ + page, +}) => { + const title = "E2E cancelled move"; + await createBooking(page, title); + + const details = page.getByRole("dialog", { name: "Booking details", exact: true }); + const beginsAt = details.getByText("Begins at", { exact: true }).locator("..").locator("dd"); + const endsAt = details.getByText("Ends at", { exact: true }).locator("..").locator("dd"); + await openBooking(page, title); + const originalStart = await beginsAt.innerText(); + const originalEnd = await endsAt.innerText(); + await page.keyboard.press("Escape"); + + for (const action of ["cancel", "escape", "outside"]) { + await dragBooking(page, title); + const confirmation = page.getByRole("dialog", { name: "Move booking?", exact: true }); + await expect(confirmation).toBeVisible(); + await expect(confirmation.getByText(title, { exact: true })).toBeVisible(); + await expect(confirmation.getByRole("button", { name: "Cancel", exact: true })).toBeFocused(); + + if (action === "cancel") { + await confirmation.getByRole("button", { name: "Cancel", exact: true }).click(); + } else if (action === "escape") { + await page.keyboard.press("Escape"); + } else { + await page.mouse.click(5, 5); + } + + await expect(confirmation).not.toBeVisible(); + await openBooking(page, title); + await expect(beginsAt).toHaveText(originalStart); + await expect(endsAt).toHaveText(originalEnd); + + await page.reload(); + await openBooking(page, title); + await expect(beginsAt).toHaveText(originalStart); + await expect(endsAt).toHaveText(originalEnd); + await page.keyboard.press("Escape"); + } + + await page.getByRole("button", { name: "Switch to Swedish", exact: true }).click(); + await dragBooking(page, title); + const confirmation = page.getByRole("dialog", { name: "Flytta bokningen?", exact: true }); + await expect(confirmation.getByText("Nuvarande tid", { exact: true })).toBeVisible(); + await expect(confirmation.getByText("Föreslagen tid", { exact: true })).toBeVisible(); + await expect( + confirmation.getByRole("button", { name: "Flytta bokning", exact: true }), + ).toBeVisible(); + await confirmation.getByRole("button", { name: "Avbryt", exact: true }).click(); + await expect(confirmation).not.toBeVisible(); + await page.getByRole("button", { name: "Byt till engelska", exact: true }).click(); + await page.reload(); + await openBooking(page, title); + await expect(beginsAt).toHaveText(originalStart); + await expect(endsAt).toHaveText(originalEnd); +}); + +test("confirming a calendar drag preserves the confirmed times when editing", async ({ page }) => { const title = "E2E moved booking"; await createBooking(page, title); - const event = page.getByRole("button").filter({ hasText: title }); - const details = page.getByRole("dialog", { name: "Booking details", exact: true }); const beginsAt = details.getByText("Begins at", { exact: true }).locator("..").locator("dd"); const endsAt = details.getByText("Ends at", { exact: true }).locator("..").locator("dd"); await openBooking(page, title); const originalStart = await beginsAt.innerText(); + const originalEnd = await endsAt.innerText(); await page.keyboard.press("Escape"); - await event.first().scrollIntoViewIfNeeded(); - const box = await event.first().boundingBox(); - if (!box) { - throw new Error("Calendar booking has no drag target"); - } - - await page.mouse.move(box.x + box.width / 2, box.y + 10); - await page.mouse.down(); - await page.mouse.move(box.x + box.width / 2, box.y + box.height + 10, { steps: 20 }); - await page.mouse.up(); + await dragBooking(page, title); + const confirmation = page.getByRole("dialog", { name: "Move booking?", exact: true }); + await expect(confirmation.getByText(title, { exact: true })).toBeVisible(); + const original = confirmation.getByText("Original time", { exact: true }).locator(".."); + await expect(original).toContainText(originalStart); + await expect(original).toContainText(originalEnd); + const proposed = confirmation.getByText("Proposed time", { exact: true }).locator(".."); + const movedStart = await proposed.locator("time").first().innerText(); + const movedEnd = await proposed.locator("time").last().innerText(); + expect(movedStart).not.toBe(originalStart); + + await confirmation.getByRole("button", { name: "Move booking", exact: true }).click(); + await expect(confirmation).not.toBeVisible(); await expect(page.getByText("Booking moved", { exact: true })).toBeVisible(); await openBooking(page, title); - await expect(beginsAt).not.toHaveText(originalStart); - const movedStart = await beginsAt.innerText(); - const movedEnd = await endsAt.innerText(); + await expect(beginsAt).toHaveText(movedStart); + await expect(endsAt).toHaveText(movedEnd); await page.getByRole("button", { name: "Edit", exact: true }).click(); @@ -92,6 +166,42 @@ test("editing immediately after a calendar drag preserves the moved times", asyn ).toBeVisible(); }); +test("a rejected calendar move keeps the original booking times", async ({ page }) => { + const title = "E2E rejected move"; + await createBooking(page, title); + await openBooking(page, title); + + const details = page.getByRole("dialog", { name: "Booking details", exact: true }); + const beginsAt = details.getByText("Begins at", { exact: true }).locator("..").locator("dd"); + const endsAt = details.getByText("Ends at", { exact: true }).locator("..").locator("dd"); + const originalStart = await beginsAt.innerText(); + const originalEnd = await endsAt.innerText(); + await page.keyboard.press("Escape"); + + await page.getByRole("link", { name: "New booking", exact: true }).click(); + await fillBooking(page, "E2E occupied slot"); + await fillSegments(page.getByRole("group", { name: "Begins at", exact: true }), { hour: 13 }); + await fillSegments(page.getByRole("group", { name: "Ends at", exact: true }), { hour: 15 }); + await page.getByRole("button", { name: "Save booking", exact: true }).click(); + await expect( + page.getByRole("button").filter({ hasText: "E2E occupied slot" }).first(), + ).toBeVisible(); + + await dragBooking(page, title); + const confirmation = page.getByRole("dialog", { name: "Move booking?", exact: true }); + await confirmation.getByRole("button", { name: "Move booking", exact: true }).click(); + await expect(page.getByText("The time slot is already taken", { exact: true })).toBeVisible(); + await expect(confirmation).not.toBeVisible(); + + await openBooking(page, title); + await expect(beginsAt).toHaveText(originalStart); + await expect(endsAt).toHaveText(originalEnd); + await page.reload(); + await openBooking(page, title); + await expect(beginsAt).toHaveText(originalStart); + await expect(endsAt).toHaveText(originalEnd); +}); + test("the selected language survives a reload", async ({ page }) => { await page.goto("/"); diff --git a/frontend/src/pages/calendar.tsx b/frontend/src/pages/calendar.tsx index c4b11c7..f60ff1a 100644 --- a/frontend/src/pages/calendar.tsx +++ b/frontend/src/pages/calendar.tsx @@ -1,9 +1,9 @@ -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { EventCalendar } from "@/components/event-calendar"; import { CalendarBlockLabel } from "@/components/calendar-block-label"; import svLocale from "@fullcalendar/react/locales/sv"; import enLocale from "@fullcalendar/react/locales/en-gb"; -import { addDays, startOfDay, startOfWeek } from "date-fns"; +import { addDays, format, startOfDay, startOfWeek } from "date-fns"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Link, useNavigate } from "@tanstack/react-router"; import { Plus } from "lucide-react"; @@ -22,16 +22,27 @@ import { parseDate, localInput } from "@/lib/dates"; import { useLanguage } from "@/lib/language"; import { useUser } from "@/lib/user"; import { Button } from "@/components/ui/button"; -import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; import { BookingDetails } from "@/pages/booking"; import { Failure } from "@/components/feedback"; +type BookingMove = { booking: BookingFragment; start: Date; end: Date }; + export function CalendarPage() { const { t, language } = useLanguage(); const { data: user } = useUser(); const navigate = useNavigate(); const cache = useQueryClient(); const [detailId, setDetailId] = useState<string | null>(null); + const [pendingMove, setPendingMove] = useState<BookingMove | null>(null); + const savingMove = useRef(false); const [viewport, setViewport] = useState(() => ({ width: window.innerWidth, height: window.innerHeight, @@ -57,15 +68,7 @@ export function CalendarPage() { event.room.some((room) => room && selectedRooms.includes(room)), ); const move = useMutation({ - mutationFn: async ({ - booking, - start, - end, - }: { - booking: BookingFragment; - start: Date; - end: Date; - }) => { + mutationFn: async ({ booking, start, end }: BookingMove) => { const { editEvent } = await request(UpdateBookingDocument, { event: { id: booking.id, @@ -88,6 +91,30 @@ export function CalendarPage() { toast.success(t("Booking moved", "Bokningen flyttades")); }, }); + + function cancelMove() { + if (!savingMove.current) { + setPendingMove(null); + } + } + + async function confirmMove() { + if (!pendingMove || savingMove.current) { + return; + } + savingMove.current = true; + try { + await move.mutateAsync(pendingMove); + } catch (error) { + toast.error( + error instanceof Error ? error.message : t("Move failed", "Flytten misslyckades"), + ); + } finally { + savingMove.current = false; + setPendingMove(null); + } + } + return ( <> <style>{bookingRoomStyles}</style> @@ -134,7 +161,7 @@ export function CalendarPage() { height={viewport.width > 600 ? Math.max(420, viewport.height - 200) : "auto"} scrollTime="17:00:00" scrollTimeReset={false} - selectable + selectable={!pendingMove && !move.isPending} eventOverlap slotEventOverlap={false} eventMaxStack={2} @@ -167,25 +194,26 @@ export function CalendarPage() { }) } eventClick={(info) => { - if (info.event.id) { + if (info.event.id && !pendingMove && !move.isPending) { setDetailId(info.event.id); } }} eventDrop={(info) => { const booking = bookings.find((event) => event.id === info.event.id); - if (!booking || !info.event.start || !info.event.end) { - info.revert(); + const start = info.event.start; + const end = info.event.end; + info.revert(); + if ( + pendingMove || + savingMove.current || + !booking || + !start || + !end || + !(user?.is_admin || user?.groups?.includes(booking.booked_as)) + ) { return; } - move.mutate( - { booking, start: info.event.start, end: info.event.end }, - { - onError: (error) => { - info.revert(); - toast.error(error.message); - }, - }, - ); + setPendingMove({ booking, start, end }); }} events={[ @@ -197,7 +225,11 @@ export function CalendarPage() { color: rooms.find((room) => event.room.includes(room.id))?.color, contrastColor: "#fff", className: bookingRoomClass(event.room), - editable: Boolean(user?.is_admin || user?.groups?.includes(event.booked_as)), + editable: Boolean( + !pendingMove && + !move.isPending && + (user?.is_admin || user?.groups?.includes(event.booked_as)), + ), durationEditable: false, })), ...calendarBlocks( @@ -222,6 +254,66 @@ export function CalendarPage() { : []), ]} /> + <Dialog + open={pendingMove !== null} + onOpenChange={(open) => { + if (!open) { + cancelMove(); + } + }} + > + <DialogContent + showCloseButton={false} + onEscapeKeyDown={(event) => { + if (savingMove.current) { + event.preventDefault(); + } + }} + onInteractOutside={(event) => { + if (savingMove.current) { + event.preventDefault(); + } + }} + > + <DialogHeader> + <DialogTitle>{t("Move booking?", "Flytta bokningen?")}</DialogTitle> + <DialogDescription>{pendingMove?.booking.title}</DialogDescription> + </DialogHeader> + {pendingMove && ( + <dl className="grid gap-4 text-sm"> + {[ + { + label: t("Original time", "Nuvarande tid"), + start: parseDate(pendingMove.booking.start), + end: parseDate(pendingMove.booking.end), + }, + { + label: t("Proposed time", "Föreslagen tid"), + start: pendingMove.start, + end: pendingMove.end, + }, + ].map(({ label, start, end }) => ( + <div key={label}> + <dt className="text-muted-foreground">{label}</dt> + <dd className="mt-2 flex flex-wrap gap-x-2 font-medium"> + <time dateTime={start.toISOString()}>{format(start, "yyyy-MM-dd HH:mm")}</time> + <span aria-hidden="true">–</span> + <time dateTime={end.toISOString()}>{format(end, "yyyy-MM-dd HH:mm")}</time> + </dd> + </div> + ))} + </dl> + )} + <DialogFooter> + <Button variant="outline" disabled={move.isPending} onClick={cancelMove}> + {t("Cancel", "Avbryt")} + </Button> + <Button disabled={move.isPending} onClick={() => void confirmMove()}> + {t("Move booking", "Flytta bokning")} + </Button> + </DialogFooter> + </DialogContent> + </Dialog> <Dialog open={detailId !== null} onOpenChange={(open) => { From 85e603a1ef1e69cc57d5eabc1a3d85e47fda152b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 13:19:37 +0200 Subject: [PATCH 29/41] Add breathing room throughout handwritten code --- backend/src/auth/session-store.ts | 8 ++++ backend/src/auth/user.ts | 3 ++ backend/src/index.ts | 22 ++++++++++ backend/src/resolvers/event.resolver.ts | 1 + backend/src/resolvers/rule.resolver.ts | 1 + backend/src/routes/index.ts | 5 +++ backend/src/services/event.service.ts | 21 +++++++++ backend/src/services/rule.service.ts | 28 ++++++++++++ backend/src/tests/rule.test.ts | 44 +++++++++++++++++++ backend/src/utils/index.ts | 2 + codegen.ts | 5 +++ e2e/admin-rules.spec.ts | 23 ++++++++++ e2e/booking-helpers.ts | 1 + e2e/booking-lifecycle.spec.ts | 9 ++++ e2e/calendar-rules.spec.ts | 9 ++++ e2e/calendar.spec.ts | 30 +++++++++++++ e2e/compose.ts | 26 +++++++++++ e2e/date-time-helpers.ts | 1 + e2e/permissions.spec.ts | 4 ++ frontend/src/api/client.ts | 6 +++ frontend/src/api/operations.graphql | 9 ++++ frontend/src/components/booking-form.tsx | 16 +++++++ frontend/src/components/date-time-fields.tsx | 3 ++ frontend/src/components/event-calendar.tsx | 3 ++ frontend/src/components/feedback.tsx | 2 + .../components/ui/event-calendar-views.tsx | 5 +++ frontend/src/lib/booking-colors.ts | 4 ++ frontend/src/lib/calendar-blocks.test.ts | 4 ++ frontend/src/lib/calendar-blocks.ts | 5 +++ frontend/src/lib/dates.test.ts | 4 ++ frontend/src/lib/dates.ts | 4 ++ frontend/src/lib/forms.ts | 1 + frontend/src/lib/language.tsx | 8 ++++ frontend/src/lib/rooms.ts | 2 + frontend/src/lib/user.ts | 4 ++ frontend/src/lib/utils.ts | 1 + frontend/src/main.tsx | 4 ++ frontend/src/pages/booking.tsx | 13 ++++++ frontend/src/pages/calendar.tsx | 18 ++++++++ frontend/src/pages/rules.tsx | 23 ++++++++++ frontend/src/router.tsx | 11 +++++ frontend/src/styles.css | 14 ++++++ frontend/vite.config.ts | 1 + playwright.config.ts | 1 + vite.config.ts | 11 ++++- 45 files changed, 419 insertions(+), 1 deletion(-) diff --git a/backend/src/auth/session-store.ts b/backend/src/auth/session-store.ts index 9ecda5c..023a92a 100644 --- a/backend/src/auth/session-store.ts +++ b/backend/src/auth/session-store.ts @@ -1,7 +1,9 @@ import type { ConfigParams } from "express-openid-connect"; type SessionConfig = Exclude<ConfigParams["session"], boolean | undefined>; + type OidcStore = NonNullable<SessionConfig["store"]>; + type Payload = Parameters<OidcStore["set"]>[1]; export interface SessionRedis { @@ -12,6 +14,7 @@ export interface SessionRedis { export function createSessionStore(client: SessionRedis): OidcStore { const key = (sid: string) => `bookit:oidc:${sid}`; + return { get(sid, callback) { void client @@ -20,7 +23,9 @@ export function createSessionStore(client: SessionRedis): OidcStore { if (!value) { return null; } + const payload = JSON.parse(value) as Payload; + return payload.header.exp > Date.now() / 1000 ? payload : null; }) .then( @@ -30,6 +35,7 @@ export function createSessionStore(client: SessionRedis): OidcStore { }, set(sid, payload, callback) { const ttl = Math.ceil(payload.header.exp - Date.now() / 1000); + const stored = { ...payload, data: { @@ -39,8 +45,10 @@ export function createSessionStore(client: SessionRedis): OidcStore { is_admin: payload.data.is_admin, }, }; + const operation = ttl > 0 ? client.set(key(sid), JSON.stringify(stored), { EX: ttl }) : client.del(key(sid)); + void operation.then( () => callback?.(), (error: unknown) => callback?.(error), diff --git a/backend/src/auth/user.ts b/backend/src/auth/user.ts index 831f72a..c30cb42 100644 --- a/backend/src/auth/user.ts +++ b/backend/src/auth/user.ts @@ -15,6 +15,7 @@ declare global { export function authenticatedUser(req: Request): User { const claims = req.oidc.user; + if ( !req.oidc.isAuthenticated() || typeof claims?.sub !== "string" || @@ -22,8 +23,10 @@ export function authenticatedUser(req: Request): User { ) { throw new GraphQLError("Authentication required", { extensions: { code: "UNAUTHENTICATED" } }); } + const claim = (name: string): string => (typeof claims[name] === "string" ? claims[name] : ""); const groups: unknown = req.appSession?.groups; + return { sub: claims.sub, cid: claims.cid, diff --git a/backend/src/index.ts b/backend/src/index.ts index 078d144..a33a774 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -15,20 +15,27 @@ interface GammaGroup { const requiredEnvironment = (name: string): string => { const value = process.env[name]; + if (!value) { throw new Error(`Missing required environment variable: ${name}`); } + return value; }; async function main() { process.env.TZ ??= "Europe/Stockholm"; + const app = express(); + app.disable("x-powered-by"); + if (process.env.TRUST_PROXY === "1") { app.set("trust proxy", 1); } + const httpServer = createServer(app); + const redis = createClient({ // Redis 5 does not support RESP3. RESP: 2, @@ -40,12 +47,15 @@ async function main() { password: process.env.REDIS_PASS || undefined, database: 1, }); + redis.on("error", (error) => console.error("Redis connection error", error)); + const prisma = new PrismaClient({ adapter: new PrismaPg({ connectionString: requiredEnvironment("DATABASE_URL") }), }); app.get("/api/health", (_req, res) => res.json({ status: "ok" })); + app.use( auth({ secret: requiredEnvironment("SESSION_SECRET"), @@ -61,6 +71,7 @@ async function main() { }, afterCallback: async (_req, _res, session) => { const userInfo = await authRequest<UserInfo>("/oauth2/userinfo", session.access_token); + const [authorities, groups] = await Promise.all([ authRequest<string[]>( `/api/client/v1/authorities/for/${encodeURIComponent(userInfo.sub)}`, @@ -69,6 +80,7 @@ async function main() { `/api/client/v1/groups/for/${encodeURIComponent(userInfo.sub)}`, ), ]); + return { ...session, is_admin: authorities.includes("admin"), @@ -85,23 +97,32 @@ async function main() { ); await Promise.all([redis.connect(), prisma.$connect()]); + const apollo = await setupRoutes(app, { prisma }, httpServer); + app.use(((error, _req, res, _next) => { console.error("Request failed", error instanceof Error ? error.message : "Unknown error"); res.status(500).json({ error: "Request failed" }); }) satisfies express.ErrorRequestHandler); + const port = Number(process.env.PORT || 8080); + await new Promise<void>((resolve, reject) => { httpServer.once("error", reject); httpServer.listen(port, resolve); }); + console.log(`BookIT listening on port ${port}`); + let stopping = false; + const stop = () => { if (stopping) { return; } + stopping = true; + void apollo .stop() .then(() => Promise.all([redis.close(), prisma.$disconnect()])) @@ -110,6 +131,7 @@ async function main() { process.exitCode = 1; }); }; + process.once("SIGTERM", stop); process.once("SIGINT", stop); } diff --git a/backend/src/resolvers/event.resolver.ts b/backend/src/resolvers/event.resolver.ts index a5be5ce..2b97db4 100644 --- a/backend/src/resolvers/event.resolver.ts +++ b/backend/src/resolvers/event.resolver.ts @@ -22,6 +22,7 @@ export const getEventQResolvers = ({ prisma }: Tools) => ({ const result = await prisma.event.findFirst({ where: { id: id }, }); + return result ? eventResult(result) : null; }, }); diff --git a/backend/src/resolvers/rule.resolver.ts b/backend/src/resolvers/rule.resolver.ts index f8dfb66..e902ddd 100644 --- a/backend/src/resolvers/rule.resolver.ts +++ b/backend/src/resolvers/rule.resolver.ts @@ -14,6 +14,7 @@ export const getRuleQResolvers = ({ prisma }: Tools) => ({ id: id, }, }); + return result ? ruleResult(result) : null; }, }); diff --git a/backend/src/routes/index.ts b/backend/src/routes/index.ts index 7b07087..21dfb0e 100644 --- a/backend/src/routes/index.ts +++ b/backend/src/routes/index.ts @@ -14,12 +14,15 @@ import { authenticatedUser } from "../auth/user"; export const setupRoutes = async (app: express.Application, tools: Tools, httpServer: Server) => { const typeDefs = mergeTypeDefs(loadFilesSync(join(__dirname, "../schemas/v1/*.gql"))); + const server = new ApolloServer<Context>({ schema: makeExecutableSchema({ typeDefs, resolvers: getResolvers(tools) }), plugins: [ApolloServerPluginDrainHttpServer({ httpServer })], includeStacktraceInErrorResponses: false, }); + await server.start(); + app.use( "/api/graphql/v1", express.json({ limit: "100kb" }), @@ -27,6 +30,8 @@ export const setupRoutes = async (app: express.Application, tools: Tools, httpSe context: async ({ req }: { req: express.Request }) => ({ user: authenticatedUser(req) }), }), ); + app.use("/", proxy(process.env.FRONTEND_URL || "http://localhost:3001")); + return server; }; diff --git a/backend/src/services/event.service.ts b/backend/src/services/event.service.ts index f1fa1d7..f0799d2 100644 --- a/backend/src/services/event.service.ts +++ b/backend/src/services/event.service.ts @@ -70,6 +70,7 @@ const overlappingEvent = async ( } let overlap_count = await prisma.event.count(query); + return overlap_count > 0; }; @@ -147,7 +148,9 @@ const validEvent = async ( if (isSerializationFailure(e)) { throw e; } + console.log(e); + return { sv: "Kunde inte kontrollera överlappande bokningar", en: "Failed to check for overlapping events", @@ -186,6 +189,7 @@ const isSerializationFailure = (error: unknown): boolean => { if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2034") { return true; } + // Driver-adapter commit failures can bypass Prisma's P2034 wrapper. return ( error instanceof globalThis.Error && @@ -203,6 +207,7 @@ export const withBookingTransaction = async ( operation: (transaction: Prisma.TransactionClient) => Promise<Error | null>, ): Promise<Error | null> => { const maxAttempts = 5; + for (let attempt = 0; attempt < maxAttempts; attempt++) { try { return await prisma.$transaction(operation, { @@ -214,11 +219,13 @@ export const withBookingTransaction = async ( if (!isSerializationFailure(error)) { throw error; } + if (attempt + 1 < maxAttempts) { await delay(20 * 2 ** attempt + Math.random() * 30); } } } + return { sv: "Bokningen ändrades samtidigt. Försök igen.", en: "A booking changed concurrently. Please try again.", @@ -238,29 +245,37 @@ export const editEvent = async ( } const id = event.id; + return withBookingTransaction(prisma, async (transaction) => { const previous = await transaction.event.findUnique({ where: { id } }); + if (!previous) { return { sv: "Kunde inte hämta gamla bokningen", en: "Failed to get event" }; } + if (!userIsInBookingGroup(previous, user)) { return { sv: "Du har inte behörighet att redigera denna bokning", en: "You do not have permission to edit this event", }; } + // Never transfer access to a previous author's phone number to the editor. const updated = { ...event, phone: event.phone ?? (previous.booked_by ? previous.phone : ""), booked_by: previous.booked_by || user.cid, }; + const error = (await validEvent(transaction, updated, user)) || (await checkRules(transaction, updated)); + if (error) { return error; } + await transaction.event.update({ where: { id }, data: toEvent(updated) }); + return null; }); }; @@ -276,13 +291,17 @@ export const createEvent = async ( en: "New bookings must not specify an existing booking ID", }; } + return withBookingTransaction(prisma, async (transaction) => { const error = (await validEvent(transaction, event, user)) || (await checkRules(transaction, event)); + if (error) { return error; } + await transaction.event.create({ data: toEvent(event) }); + return null; }); }; @@ -294,6 +313,7 @@ export const deleteEvent = async (prisma: PrismaClient, id: string, user: User) id: id, }, }); + if (!event) { return { sv: "Kunde ej hitta bokningen", @@ -313,6 +333,7 @@ export const deleteEvent = async (prisma: PrismaClient, id: string, user: User) id: id, }, }); + return null; }); }; diff --git a/backend/src/services/rule.service.ts b/backend/src/services/rule.service.ts index 7c40a59..17232d9 100644 --- a/backend/src/services/rule.service.ts +++ b/backend/src/services/rule.service.ts @@ -29,6 +29,7 @@ export const day = (date: Date): string => { export const dayApplies = (date: Date, day_mask: number): boolean => { const dayIndex = (Math.floor(date.getDay()) + 6) % 7; + return (day_mask >> dayIndex) % 2 > 0; }; @@ -50,18 +51,23 @@ export const toExplicitRules = (rules: rule[], from: Date, to: Date): ExplicitRu if (!Number.isFinite(from.getTime()) || !Number.isFinite(to.getTime()) || from > to) { return []; } + for (const rule of rules) { const current = new Date(Math.max(from.getTime(), rule.start_date.getTime())); const end = new Date(Math.min(to.getTime(), rule.end_date.getTime())); + current.setHours(0, 0, 0, 0); end.setHours(0, 0, 0, 0); + while (current <= end) { if (dayApplies(current, rule.day_mask)) { insertRule(explicitRules, current, rule); } + current.setDate(current.getDate() + 1); } } + return explicitRules.sort((a, b): number => a.priority - b.priority); }; @@ -78,13 +84,16 @@ const mergeIntoList = ( if (rule.start >= rule.end) { return nextMergedRule ? [nextMergedRule, ...mergedRules] : []; } + if (nextMergedRule == undefined) { return [rule]; } + if (nextMergedRule.start > rule.start) { if (nextMergedRule.start >= rule.end) { return [rule, nextMergedRule, ...mergedRules]; } + return [ { ...rule, end: new Date(nextMergedRule.start) }, ...mergeIntoList({ ...rule, start: new Date(nextMergedRule.end) }, [ @@ -93,23 +102,28 @@ const mergeIntoList = ( ]), ]; } + if (nextMergedRule.end > rule.start) { return [nextMergedRule, ...mergeIntoList({ ...rule, start: nextMergedRule.end }, mergedRules)]; } + return [nextMergedRule, ...mergeIntoList(rule, mergedRules)]; }; export const mergeRules = (rules: ExplicitRule[]): ExplicitRule[] => { let mergedRules: ExplicitRule[] = []; + for (const rule of rules) { mergedRules = mergeIntoList(rule, mergedRules); } + return mergedRules; }; const breaksExplicitRule = (rule: ExplicitRule, event: InputEvent): boolean => { const start = new Date(event.start); const end = new Date(event.end); + return rule.start < end && rule.end > start && !rule.allow; }; @@ -120,6 +134,7 @@ export const doesObeyRules = (rules: rule[], event: InputEvent): Error | null => for (const room of event.room) { const roomRules = rules.filter((rule) => rule.room.includes(room)); const explicitRules = mergeRules(toExplicitRules(roomRules, start, end)); + for (const rule of explicitRules) { if (breaksExplicitRule(rule, event)) { return { @@ -129,6 +144,7 @@ export const doesObeyRules = (rules: rule[], event: InputEvent): Error | null => } } } + return null; }; @@ -160,6 +176,7 @@ export const checkRules = async (prisma: Prisma.TransactionClient, event: InputE ...ruleDateBounds(new Date(event.start), new Date(event.end)), }, }); + return doesObeyRules(rules, event); }; @@ -182,19 +199,23 @@ export const createRule = async ( en: "You do not have permission to create rules", }; } + const start = new Date(rule.start_date); const end = new Date(rule.end_date); + if (!validDate(start, end)) { return { sv: "Ogiltigt datum", en: "Invalid date", }; } + rule.start_date = day(start); rule.end_date = day(end); const start_time = new Date(rule.start_date + "T" + rule.start_time); const end_time = new Date(rule.start_date + "T" + rule.end_time); + if (!validDateTime(start_time, end_time)) { return { sv: "Ogiltig tid", @@ -216,12 +237,14 @@ export const createRule = async ( end_date: new Date(rule.end_date), }, }); + if (!res) { return { sv: "Kunde inte skapa regel", en: "Could not create rule", }; } + return null; }; @@ -236,9 +259,11 @@ export const deleteRule = async ( en: "You do not have permission to delete rules", }; } + const rule = await prisma.rule.findUnique({ where: { id: id }, }); + if (!rule) { return { sv: "Kunde inte hitta regel", @@ -253,12 +278,15 @@ export const deleteRule = async ( }, }), ); + if (err) { console.log(err); + return { sv: "Kunde inte ta bort regel", en: "Could not delete rule", }; } + return null; }; diff --git a/backend/src/tests/rule.test.ts b/backend/src/tests/rule.test.ts index 7a8a5e5..823b907 100644 --- a/backend/src/tests/rule.test.ts +++ b/backend/src/tests/rule.test.ts @@ -59,16 +59,19 @@ describe("Rule utility functions", () => { it("Should create ISO date", () => { const date = new Date("2021-08-19"); const time = "17:00"; + assert.equal( new Date("2021-08-19T17:00").toISOString(), new Date(day(date) + "T" + time).toISOString(), ); }); + it("Should allow day", () => { assert.equal(dayApplies(new Date("2021-08-19"), 0b0001000), true, "0b0001000"); assert.equal(dayApplies(new Date("2021-08-19"), 0b0001100), true, "0b0001100"); assert.equal(dayApplies(new Date("2021-08-19"), 0b0011000), true, "0b0011000"); }); + it("Should not allow day", () => { assert.equal(dayApplies(new Date("2021-08-19"), 0b1110111), false, "0b1110111"); assert.equal(dayApplies(new Date("2021-08-19"), 0b0000000), false, "0b0000000"); @@ -89,17 +92,22 @@ describe("Rules to ExplicitRules", () => { toExplicitRules(dummyRules, new Date("2021-08-20"), new Date("2021-08-19")), [], ); + assert.deepEqual(toExplicitRules(dummyRules, new Date("invalid"), new Date("2021-08-19")), []); }); + it("does not expand a rule outside its effective dates", () => { const rules = [ { ...dummyRules[0], start_date: new Date("2021-08-19"), end_date: new Date("2021-08-20") }, ]; + const result = toExplicitRules(rules, new Date("2021-08-01"), new Date("2021-08-31")); + assert.equal(result.length, 2); assert.equal(day(result[0].start), "2021-08-19"); assert.equal(day(result[1].start), "2021-08-20"); }); + it("Should create one mini rule", () => { const expected: ExplicitRule[] = [ { @@ -108,14 +116,17 @@ describe("Rules to ExplicitRules", () => { end: new Date("2021-08-20T17:00"), }, ]; + const got: ExplicitRule[] = toExplicitRules( [dummyRules[0]], new Date("2021-08-20"), new Date("2021-08-20"), ); + assert.equal(got.length, expected.length); assertExplicitRuleEqual(got[0], expected[0]); }); + it("Should create two mini rules", () => { const expected: ExplicitRule[] = [ { @@ -129,15 +140,18 @@ describe("Rules to ExplicitRules", () => { end: new Date("2021-08-20T17:00"), }, ]; + const got: ExplicitRule[] = toExplicitRules( [dummyRules[0]], new Date("2021-08-19"), new Date("2021-08-20"), ); + assert.equal(got.length, expected.length); assertExplicitRuleEqual(got[0], expected[0]); assertExplicitRuleEqual(got[1], expected[1]); }); + it("Should create two mini rules, one masked out", () => { const expected: ExplicitRule[] = [ { @@ -151,11 +165,13 @@ describe("Rules to ExplicitRules", () => { end: new Date("2021-08-18T17:00"), }, ]; + const got: ExplicitRule[] = toExplicitRules( [dummyRules[0], dummyRules[1]], new Date("2021-08-17"), new Date("2021-08-18"), ); + assert.equal(got.length, expected.length); assertExplicitRuleEqual(got[0], expected[0]); assertExplicitRuleEqual(got[1], expected[1]); @@ -168,16 +184,20 @@ describe("Booking rule scope", () => { new Date("2026-09-06T12:00:00+02:00"), new Date("2026-09-06T13:00:00+02:00"), ); + assert.equal(bounds.end_date.gte.toISOString(), "2026-09-06T00:00:00.000Z"); assert.equal(bounds.start_date.lte.toISOString(), "2026-09-06T00:00:00.000Z"); }); + it("uses the Stockholm calendar date even when UTC is on the previous day", () => { const bounds = ruleDateBounds( new Date("2026-10-24T22:30:00Z"), new Date("2026-10-24T23:30:00Z"), ); + assert.equal(bounds.end_date.gte.toISOString(), "2026-10-25T00:00:00.000Z"); }); + it("does not let permission in one room override restrictions in another", () => { const booking: InputEvent = { title: "Two rooms", @@ -188,6 +208,7 @@ describe("Booking rule scope", () => { booked_as: "digit", booking_terms: true, }; + const base = { ...defaultRule, start_date: new Date("2021-08-01"), @@ -195,6 +216,7 @@ describe("Booking rule scope", () => { start_time: "08:00", end_time: "17:00", }; + const allowed: rule = { ...base, title: "Hub permission", @@ -202,6 +224,7 @@ describe("Booking rule scope", () => { allow: true, priority: 1, }; + const denied: rule = { ...base, title: "Group room closed", @@ -209,10 +232,12 @@ describe("Booking rule scope", () => { allow: false, priority: 10, }; + assert.equal( doesObeyRules([allowed, denied], booking)?.en, "Booking breaks rule: Group room closed", ); + assert.equal(doesObeyRules([allowed, denied], { ...booking, room: ["BIG_HUB"] }), null); }); }); @@ -225,12 +250,14 @@ describe("Merge rules", () => { end: new Date("2021-08-20T10:00"), priority: 1, }; + const later = { ...defaultExplicitRules, start: new Date("2021-08-20T14:00"), end: new Date("2021-08-20T16:00"), priority: 1, }; + const covered = { ...defaultExplicitRules, start: new Date("2021-08-20T08:00"), @@ -238,13 +265,17 @@ describe("Merge rules", () => { priority: 2, allow: true, }; + assert.deepEqual(mergeRules([first, later, covered]), [first, later]); }); + it("No rules", () => { const expected: ExplicitRule[] = []; const got = mergeRules(toExplicitRules([], new Date("2021-08-18"), new Date("2021-08-20"))); + assert.deepEqual(got, expected); }); + it("One rule", () => { const expected: ExplicitRule[] = [ { @@ -253,6 +284,7 @@ describe("Merge rules", () => { end: new Date("2021-08-20T17:00"), }, ]; + const got = mergeRules( toExplicitRules([dummyRules[0]], new Date("2021-08-20"), new Date("2021-08-21")), ); @@ -260,6 +292,7 @@ describe("Merge rules", () => { assert.equal(expected.length, got.length); assertExplicitRuleEqual(expected[0], got[0]); }); + /** * --------- * +++ @@ -285,6 +318,7 @@ describe("Merge rules", () => { allow: true, }, ]; + const expected: ExplicitRule[] = [ { ...rules[1], @@ -297,11 +331,14 @@ describe("Merge rules", () => { end: new Date("2021-08-20T17:00"), }, ]; + const got = mergeRules(toExplicitRules(rules, new Date("2021-08-20"), new Date("2021-08-20"))); + assert.equal(got.length, expected.length); assertExplicitRuleEqual(expected[0], got[0]); assertExplicitRuleEqual(expected[1], got[1]); }); + /** * --------- * +++ @@ -327,6 +364,7 @@ describe("Merge rules", () => { allow: true, }, ]; + const expected: ExplicitRule[] = [ { ...rules[0], @@ -344,12 +382,15 @@ describe("Merge rules", () => { end: new Date("2021-08-20T17:00"), }, ]; + const got = mergeRules(toExplicitRules(rules, new Date("2021-08-20"), new Date("2021-08-20"))); + assert.equal(got.length, expected.length); assertExplicitRuleEqual(expected[0], got[0]); assertExplicitRuleEqual(expected[1], got[1]); assertExplicitRuleEqual(expected[2], got[2]); }); + /** * ++++ * --------- @@ -385,6 +426,7 @@ describe("Merge rules", () => { allow: true, }, ]; + const expected: ExplicitRule[] = [ { ...rules[0], @@ -407,7 +449,9 @@ describe("Merge rules", () => { end: new Date("2021-08-20T19:00"), }, ]; + const got = mergeRules(toExplicitRules(rules, new Date("2021-08-20"), new Date("2021-08-20"))); + assert.equal(got.length, expected.length); assertExplicitRuleEqual(expected[0], got[0]); assertExplicitRuleEqual(expected[1], got[1]); diff --git a/backend/src/utils/index.ts b/backend/src/utils/index.ts index 178a233..0e49425 100644 --- a/backend/src/utils/index.ts +++ b/backend/src/utils/index.ts @@ -7,9 +7,11 @@ export const authRequest = async <T>(endpoint: string, accessToken?: string): Pr const headers = { Authorization: accessToken ? `Bearer ${accessToken}` : `pre-shared ${process.env.API_KEY}`, }; + const response = await axios.get<T>(`${process.env.ISSUER_BASE_URL}${endpoint}`, { headers, timeout: 10_000, }); + return response.data; }; diff --git a/codegen.ts b/codegen.ts index aec6271..fe91e2f 100644 --- a/codegen.ts +++ b/codegen.ts @@ -11,10 +11,13 @@ import { type DocumentNode } from "graphql"; async function generate() { const schema = mergeTypeDefs(loadFilesSync("backend/src/schemas/v1/*.gql")); + const documents = loadFilesSync<DocumentNode>("frontend/src/**/*.graphql").map((document) => ({ document, })); + const config = { useTypeImports: true, enumsAsTypes: true }; + const outputs = [ { filename: "frontend/src/generated/graphql.ts", @@ -39,12 +42,14 @@ async function generate() { }), }, ]; + for (const { filename, content } of outputs) { await mkdir(dirname(filename), { recursive: true }); await writeFile(filename, `${content.trimEnd()}\n`); console.log(`Generated ${filename}`); } } + void generate().catch((error) => { console.error(error); process.exitCode = 1; diff --git a/e2e/admin-rules.spec.ts b/e2e/admin-rules.spec.ts index fc4a871..809fea5 100644 --- a/e2e/admin-rules.spec.ts +++ b/e2e/admin-rules.spec.ts @@ -12,16 +12,19 @@ test("the rule calendar returns keyboard focus without closing its parent dialog const form = page.getByRole("dialog", { name: "New rule", exact: true }); const trigger = form.getByRole("button", { name: "Choose date for End date", exact: true }); + await trigger.press("Enter"); const calendar = page.getByRole("dialog", { name: "End date calendar", exact: true }); const day = calendar.getByRole("button", { name: /31 December 2040 selected/ }); + await expect(day).toBeFocused(); await day.press("ArrowLeft"); await page.keyboard.press("Enter"); await expect(calendar).not.toBeVisible(); await expect(trigger).toBeFocused(); + await expectSegments(form.getByRole("group", { name: "End date", exact: true }), { year: 2040, month: 12, @@ -40,35 +43,44 @@ test("a new rule has the original defaults and requires a selected weekday", asy await expect(page.getByRole("heading", { name: "Rules", exact: true })).toBeVisible(); await page.getByRole("button", { name: "New rule", exact: true }).click(); + const form = page.getByRole("dialog", { name: "New rule", exact: true }); await expect(form.getByRole("spinbutton", { name: "Priority", exact: true })).toHaveValue("10"); + await expect(form.getByRole("combobox", { name: "Availability", exact: true })).toHaveValue( "true", ); + await expectSegments(form.getByRole("group", { name: "End date", exact: true }), { year: 2040, month: 12, day: 31, }); + await expect(form.getByRole("checkbox", { checked: true })).toHaveCount(0); await form.getByRole("textbox", { name: "Title", exact: true }).fill("E2E room maintenance"); await form.getByRole("spinbutton", { name: "Priority", exact: true }).fill("2"); await form.getByRole("combobox", { name: "Availability", exact: true }).selectOption("false"); + await fillSegments( form.getByRole("group", { name: "Start date", exact: true }), dateSegments(await bookingDate(page)), ); + await fillSegments(form.getByRole("group", { name: "Start time", exact: true }), { hour: 12, minute: 0, }); + await fillSegments(form.getByRole("group", { name: "End time", exact: true }), { hour: 13, minute: 0, }); + await form.getByRole("checkbox", { name: "Storhubben", exact: true }).check(); + await form .getByRole("textbox", { name: "Description", exact: true }) .fill("The room is closed for maintenance."); @@ -82,24 +94,30 @@ test("an administrator creates, views and deletes a recurring booking rule", asy await expect(page.getByRole("heading", { name: "Rules", exact: true })).toBeVisible(); await page.getByRole("button", { name: "New rule", exact: true }).click(); + const form = page.getByRole("dialog", { name: "New rule", exact: true }); await form.getByRole("textbox", { name: "Title", exact: true }).fill("E2E room maintenance"); await form.getByRole("spinbutton", { name: "Priority", exact: true }).fill("2"); await form.getByRole("combobox", { name: "Availability", exact: true }).selectOption("false"); + await fillSegments( form.getByRole("group", { name: "Start date", exact: true }), dateSegments(await bookingDate(page)), ); + await fillSegments(form.getByRole("group", { name: "Start time", exact: true }), { hour: 12, minute: 0, }); + await fillSegments(form.getByRole("group", { name: "End time", exact: true }), { hour: 13, minute: 0, }); + await form.getByRole("checkbox", { name: "Storhubben", exact: true }).check(); + await form .getByRole("textbox", { name: "Description", exact: true }) .fill("The room is closed for maintenance."); @@ -121,14 +139,17 @@ test("an administrator creates, views and deletes a recurring booking rule", asy await expect(row).toContainText("Mon · Tue · Wed · Thu · Fri · Sat · Sun"); await row.getByRole("button", { name: "Details E2E room maintenance", exact: true }).click(); + const details = page.getByRole("dialog", { name: "Rule details", exact: true }); await expect( details.getByText("The room is closed for maintenance.", { exact: true }), ).toBeVisible(); + await expect( details.getByText("Created", { exact: true }).locator("..").locator("dd"), ).toContainText(/\d{4}/); + await expect( details.getByText("Updated", { exact: true }).locator("..").locator("dd"), ).toContainText(/\d{4}/); @@ -137,10 +158,12 @@ test("an administrator creates, views and deletes a recurring booking rule", asy await expect(details).not.toBeVisible(); await row.getByRole("button", { name: "Delete E2E room maintenance", exact: true }).click(); + await page .getByRole("dialog", { name: "Delete rule?" }) .getByRole("button", { name: "Confirm deletion", exact: true }) .click(); + await expect(row).toHaveCount(0); await page.reload(); diff --git a/e2e/booking-helpers.ts b/e2e/booking-helpers.ts index 7163e8f..f50e141 100644 --- a/e2e/booking-helpers.ts +++ b/e2e/booking-helpers.ts @@ -32,6 +32,7 @@ export async function fillBooking(page: Page, title: string) { hour: 12, minute: 0, }); + await fillSegments(page.getByRole("group", { name: "Ends at", exact: true }), { ...dateSegments(date), hour: 13, diff --git a/e2e/booking-lifecycle.spec.ts b/e2e/booking-lifecycle.spec.ts index d27faf8..b7741a0 100644 --- a/e2e/booking-lifecycle.spec.ts +++ b/e2e/booking-lifecycle.spec.ts @@ -16,13 +16,17 @@ test("a group member creates, edits and deletes a persisted booking", async ({ p await expect(page.getByRole("heading", { name: title, exact: true })).toBeVisible(); await page.getByRole("button", { name: "Edit", exact: true }).click(); + await expect(page.getByRole("textbox", { name: "Phone number", exact: true })).toHaveValue( "0701234567", ); + await page.getByRole("textbox", { name: "Title", exact: true }).fill(`${title} updated`); + await page .getByRole("textbox", { name: "Description", exact: true }) .fill("Agenda updated by a group member."); + await acceptConditions(page); await page.getByRole("button", { name: "Save booking", exact: true }).click(); await expect(page).toHaveURL(/\/$/); @@ -31,7 +35,9 @@ test("a group member creates, edits and deletes a persisted booking", async ({ p await expect(page.getByText("Agenda updated by a group member.", { exact: true })).toBeVisible(); await page.getByRole("button", { name: "Delete", exact: true }).click(); + const dialog = page.getByRole("dialog", { name: "Delete booking?" }); + await dialog.getByRole("button", { name: "Cancel", exact: true }).click(); await expect(page.getByRole("heading", { name: `${title} updated`, exact: true })).toBeVisible(); @@ -47,6 +53,7 @@ test("a group member creates, edits and deletes a persisted booking", async ({ p test("two booking forms cannot reserve the same room and time twice", async ({ page }) => { const otherPage = await page.context().newPage(); + const bookings = [ { page, title: "E2E concurrent booking A" }, { page: otherPage, title: "E2E concurrent booking B" }, @@ -54,6 +61,7 @@ test("two booking forms cannot reserve the same room and time twice", async ({ p try { await otherPage.goto("/"); + for (const { page: bookingPage, title } of bookings) { await bookingPage.getByRole("link", { name: "New booking", exact: true }).click(); await fillBooking(bookingPage, title); @@ -71,6 +79,7 @@ test("two booking forms cannot reserve the same room and time twice", async ({ p const winner = bookings.find(({ page }) => new URL(page.url()).pathname === "/"); const loser = bookings.find(({ page }) => new URL(page.url()).pathname !== "/"); + if (!winner || !loser) { throw new Error("Expected one saved booking and one rejected booking form"); } diff --git a/e2e/calendar-rules.spec.ts b/e2e/calendar-rules.spec.ts index 979edad..effd130 100644 --- a/e2e/calendar-rules.spec.ts +++ b/e2e/calendar-rules.spec.ts @@ -16,21 +16,26 @@ test("overlapping room restrictions share a compact label and respect room filte { title: "Reading", rooms: ["CTC"] }, ]) { await page.getByRole("button", { name: "New rule", exact: true }).click(); + const form = page.getByRole("dialog", { name: "New rule", exact: true }); await form.getByRole("textbox", { name: "Title", exact: true }).fill(title); + await form .getByRole("textbox", { name: "Description", exact: true }) .fill("The rooms are unavailable during this time."); + await form.getByRole("combobox", { name: "Availability", exact: true }).selectOption("false"); for (const name of ["Start date", "End date"]) { await fillSegments(form.getByRole("group", { name, exact: true }), dateSegments(date)); } + await fillSegments(form.getByRole("group", { name: "Start time", exact: true }), { hour: 12, minute: 0, }); + await fillSegments(form.getByRole("group", { name: "End time", exact: true }), { hour: 13, minute: 0, @@ -39,6 +44,7 @@ test("overlapping room restrictions share a compact label and respect room filte for (const room of rooms) { await form.getByRole("checkbox", { name: room, exact: true }).check(); } + for (const weekday of ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]) { await form.getByRole("checkbox", { name: weekday, exact: true }).check(); } @@ -49,6 +55,7 @@ test("overlapping room restrictions share a compact label and respect room filte } await page.getByRole("navigation").getByRole("link", { name: "Calendar", exact: true }).click(); + const label = page.getByRole("button", { name: "Maintenance · Reading", exact: true }); await expect(label).toHaveCount(1); @@ -56,7 +63,9 @@ test("overlapping room restrictions share a compact label and respect room filte await expect(label).toHaveCSS("white-space", "nowrap"); await label.hover(); + const tooltip = page.getByRole("tooltip"); + await expect(tooltip).toContainText("Maintenance — The rooms are unavailable during this time."); await expect(tooltip).toContainText("Reading — The rooms are unavailable during this time."); await page.keyboard.press("Escape"); diff --git a/e2e/calendar.spec.ts b/e2e/calendar.spec.ts index cee5c8c..e57e1e2 100644 --- a/e2e/calendar.spec.ts +++ b/e2e/calendar.spec.ts @@ -5,8 +5,11 @@ import type { Page } from "@playwright/test"; async function dragBooking(page: Page, title: string) { const event = page.getByRole("button").filter({ hasText: title }).first(); + await event.scrollIntoViewIfNeeded(); + const box = await event.boundingBox(); + if (!box) { throw new Error("Calendar booking has no drag target"); } @@ -24,6 +27,7 @@ test("room filters and period navigation show the expected bookings", async ({ p const event = page.getByRole("button").filter({ hasText: title }); const roomFilter = page.getByRole("button", { name: "Storhubben", exact: true }); + await expect(roomFilter).toHaveAttribute("aria-pressed", "true"); await roomFilter.click(); await expect(roomFilter).toHaveAttribute("aria-pressed", "false"); @@ -48,19 +52,25 @@ test("cancelling or dismissing a dragged booking preserves its original times", page, }) => { const title = "E2E cancelled move"; + await createBooking(page, title); const details = page.getByRole("dialog", { name: "Booking details", exact: true }); const beginsAt = details.getByText("Begins at", { exact: true }).locator("..").locator("dd"); const endsAt = details.getByText("Ends at", { exact: true }).locator("..").locator("dd"); + await openBooking(page, title); + const originalStart = await beginsAt.innerText(); const originalEnd = await endsAt.innerText(); + await page.keyboard.press("Escape"); for (const action of ["cancel", "escape", "outside"]) { await dragBooking(page, title); + const confirmation = page.getByRole("dialog", { name: "Move booking?", exact: true }); + await expect(confirmation).toBeVisible(); await expect(confirmation.getByText(title, { exact: true })).toBeVisible(); await expect(confirmation.getByRole("button", { name: "Cancel", exact: true })).toBeFocused(); @@ -87,12 +97,16 @@ test("cancelling or dismissing a dragged booking preserves its original times", await page.getByRole("button", { name: "Switch to Swedish", exact: true }).click(); await dragBooking(page, title); + const confirmation = page.getByRole("dialog", { name: "Flytta bokningen?", exact: true }); + await expect(confirmation.getByText("Nuvarande tid", { exact: true })).toBeVisible(); await expect(confirmation.getByText("Föreslagen tid", { exact: true })).toBeVisible(); + await expect( confirmation.getByRole("button", { name: "Flytta bokning", exact: true }), ).toBeVisible(); + await confirmation.getByRole("button", { name: "Avbryt", exact: true }).click(); await expect(confirmation).not.toBeVisible(); await page.getByRole("button", { name: "Byt till engelska", exact: true }).click(); @@ -112,19 +126,27 @@ test("confirming a calendar drag preserves the confirmed times when editing", as const endsAt = details.getByText("Ends at", { exact: true }).locator("..").locator("dd"); await openBooking(page, title); + const originalStart = await beginsAt.innerText(); const originalEnd = await endsAt.innerText(); + await page.keyboard.press("Escape"); await dragBooking(page, title); + const confirmation = page.getByRole("dialog", { name: "Move booking?", exact: true }); + await expect(confirmation.getByText(title, { exact: true })).toBeVisible(); + const original = confirmation.getByText("Original time", { exact: true }).locator(".."); + await expect(original).toContainText(originalStart); await expect(original).toContainText(originalEnd); + const proposed = confirmation.getByText("Proposed time", { exact: true }).locator(".."); const movedStart = await proposed.locator("time").first().innerText(); const movedEnd = await proposed.locator("time").last().innerText(); + expect(movedStart).not.toBe(originalStart); await confirmation.getByRole("button", { name: "Move booking", exact: true }).click(); @@ -142,6 +164,7 @@ test("confirming a calendar drag preserves the confirmed times when editing", as ["Ends at", movedEnd], ] as const) { const date = new Date(value.replace(" ", "T")); + await expectSegments(page.getByRole("group", { name, exact: true }), { year: date.getFullYear(), month: date.getMonth() + 1, @@ -154,6 +177,7 @@ test("confirming a calendar drag preserves the confirmed times when editing", as await page .getByRole("textbox", { name: "Description", exact: true }) .fill("Updated after moving the booking."); + await page.getByRole("button", { name: "Save booking", exact: true }).click(); await expect(page).toHaveURL(/\/$/); @@ -161,6 +185,7 @@ test("confirming a calendar drag preserves the confirmed times when editing", as await openBooking(page, title); await expect(beginsAt).toHaveText(movedStart); await expect(endsAt).toHaveText(movedEnd); + await expect( details.getByText("Updated after moving the booking.", { exact: true }), ).toBeVisible(); @@ -168,6 +193,7 @@ test("confirming a calendar drag preserves the confirmed times when editing", as test("a rejected calendar move keeps the original booking times", async ({ page }) => { const title = "E2E rejected move"; + await createBooking(page, title); await openBooking(page, title); @@ -176,6 +202,7 @@ test("a rejected calendar move keeps the original booking times", async ({ page const endsAt = details.getByText("Ends at", { exact: true }).locator("..").locator("dd"); const originalStart = await beginsAt.innerText(); const originalEnd = await endsAt.innerText(); + await page.keyboard.press("Escape"); await page.getByRole("link", { name: "New booking", exact: true }).click(); @@ -183,12 +210,15 @@ test("a rejected calendar move keeps the original booking times", async ({ page await fillSegments(page.getByRole("group", { name: "Begins at", exact: true }), { hour: 13 }); await fillSegments(page.getByRole("group", { name: "Ends at", exact: true }), { hour: 15 }); await page.getByRole("button", { name: "Save booking", exact: true }).click(); + await expect( page.getByRole("button").filter({ hasText: "E2E occupied slot" }).first(), ).toBeVisible(); await dragBooking(page, title); + const confirmation = page.getByRole("dialog", { name: "Move booking?", exact: true }); + await confirmation.getByRole("button", { name: "Move booking", exact: true }).click(); await expect(page.getByText("The time slot is already taken", { exact: true })).toBeVisible(); await expect(confirmation).not.toBeVisible(); diff --git a/e2e/compose.ts b/e2e/compose.ts index 47be3e9..161315a 100644 --- a/e2e/compose.ts +++ b/e2e/compose.ts @@ -34,10 +34,13 @@ const users = { } as const; export type UserRole = keyof typeof users; + const testPassword = "password1337"; const groupId = "aed27030-ad90-4526-855c-1e909b1dcecb"; + const postId = "7bb1db15-730d-4864-bfc3-99abe7c0ccf8"; + const seed = { users: Object.values(users).map((user) => ({ id: user.id, @@ -71,9 +74,12 @@ export interface Environment { async function unusedPort(): Promise<number> { return new Promise((resolve, reject) => { const server = createServer(); + server.on("error", reject); + server.listen(0, "127.0.0.1", () => { const port = (server.address() as AddressInfo).port; + server.close((error) => (error ? reject(error) : resolve(port))); }); }); @@ -84,6 +90,7 @@ async function provisionClient(browser: Browser, gammaUrl: string, appUrl: strin try { const page = await context.newPage(); + page.setDefaultTimeout(30_000); await page.goto(`${gammaUrl}/login`); await page.locator('[name="username"]').fill("admin"); @@ -101,14 +108,18 @@ async function provisionClient(browser: Browser, gammaUrl: string, appUrl: strin await expect(page.getByText("Client details", { exact: true })).toBeVisible(); const clientId = (await page.locator('li:has-text("Client id:") span').innerText()).trim(); + const credentials = page .locator("article") .filter({ has: page.locator("header", { hasText: /^\s*Credentials\s*$/ }) }); + const clientSecret = (await credentials.locator("code").first().innerText()).trim(); + const authorization = await credentials .locator("code") .filter({ hasText: "Authorization: pre-shared" }) .innerText(); + const apiKey = authorization.replace("Authorization: pre-shared", "").trim(); if (!clientId || !clientSecret || !/^[\w-]+:\S+$/.test(apiKey)) { @@ -120,6 +131,7 @@ async function provisionClient(browser: Browser, gammaUrl: string, appUrl: strin await page.getByRole("button", { name: "Add user authority", exact: true }).click(); await page.locator(".authority-user-row").selectOption(users.admin.id); await page.getByRole("button", { name: "Save", exact: true }).click(); + await expect( page.locator("article").filter({ has: page.locator("header", { hasText: /^\s*admin\s*$/ }) }), ).toBeVisible(); @@ -159,9 +171,11 @@ export async function loginAs( export async function compose(browser: Browser): Promise<Environment> { const frontendImage = process.env.BOOKIT_FRONTEND_IMAGE; const backendImage = process.env.BOOKIT_BACKEND_IMAGE; + if (!frontendImage || !backendImage) { throw new Error("Run E2E with make -C e2e run-e2e"); } + const network = await new Network().start(); const containers: StartedTestContainer[] = []; const logs: Record<string, string> = {}; @@ -171,12 +185,15 @@ export async function compose(browser: Browser): Promise<Environment> { const text = String(chunk) .replace(/(password:)\S+/g, "$1[REDACTED]") .replace(/(and code:)\s*\S+/g, "$1 [REDACTED]"); + logs[name] = ((logs[name] ?? "") + text).slice(-100_000); }; const track = async <T extends StartedTestContainer>(pending: Promise<T>): Promise<T> => { const container = await pending; + containers.push(container); + return container; }; @@ -184,7 +201,9 @@ export async function compose(browser: Browser): Promise<Environment> { if (stopped) { return; } + stopped = true; + const failures: unknown[] = []; for (const container of containers.toReversed()) { @@ -208,6 +227,7 @@ export async function compose(browser: Browser): Promise<Environment> { try { console.log("Starting isolated PostgreSQL, Redis and Gamma containers..."); + const gammaDb = await track( new PostgreSqlContainer(images.gammaPostgres) .withNetwork(network) @@ -286,6 +306,7 @@ export async function compose(browser: Browser): Promise<Environment> { ); console.log("Provisioning BookIT OAuth client and roles through Gamma..."); + const client = await provisionClient(browser, gammaUrl, appUrl); const env = { @@ -308,10 +329,13 @@ export async function compose(browser: Browser): Promise<Environment> { }; record("images", JSON.stringify({ frontend: frontendImage, backend: backendImage })); + console.log( `Using BookIT images: ${JSON.stringify({ frontend: frontendImage, backend: backendImage })}`, ); + console.log("Initializing the isolated database with the backend image..."); + await track( new GenericContainer(backendImage) .withPlatform("linux/amd64") @@ -323,6 +347,7 @@ export async function compose(browser: Browser): Promise<Environment> { .withStartupTimeout(120_000) .start(), ); + await track( new GenericContainer(frontendImage) .withPlatform("linux/amd64") @@ -336,6 +361,7 @@ export async function compose(browser: Browser): Promise<Environment> { .withStartupTimeout(120_000) .start(), ); + await track( new GenericContainer(backendImage) .withPlatform("linux/amd64") diff --git a/e2e/date-time-helpers.ts b/e2e/date-time-helpers.ts index 385d548..b6f18aa 100644 --- a/e2e/date-time-helpers.ts +++ b/e2e/date-time-helpers.ts @@ -4,6 +4,7 @@ type Segments = Partial<Record<"year" | "month" | "day" | "hour" | "minute", num export function dateSegments(value: string): Segments { const [year, month, day] = value.split("-").map(Number); + if (year === undefined || month === undefined || day === undefined) { throw new Error(`Expected an ISO date, received ${value}`); } diff --git a/e2e/permissions.spec.ts b/e2e/permissions.spec.ts index 3b0286f..57c81c5 100644 --- a/e2e/permissions.spec.ts +++ b/e2e/permissions.spec.ts @@ -11,9 +11,11 @@ test("an outsider can view bookings but cannot access editing or administration" await expect(page.getByText("BookIT Guest", { exact: true })).toBeVisible(); await page.getByRole("link", { name: "New booking", exact: true }).click(); + await expect(page.getByRole("alert")).toHaveText( "You need membership in an active group to make a booking.", ); + await expect(page.getByRole("button", { name: "Save booking", exact: true })).toHaveCount(0); await page.getByRole("navigation").getByRole("link", { name: "Rules", exact: true }).click(); @@ -22,9 +24,11 @@ test("an outsider can view bookings but cannot access editing or administration" await page.getByRole("navigation").getByRole("link", { name: "Calendar", exact: true }).click(); await openBooking(page, "E2E protected group booking"); + await expect( page.getByRole("heading", { name: "E2E protected group booking", exact: true }), ).toBeVisible(); + await expect(page.getByRole("button", { name: "Edit", exact: true })).toHaveCount(0); await expect(page.getByRole("button", { name: "Delete", exact: true })).toHaveCount(0); }); diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index c4a5cc6..5ad005a 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -11,19 +11,25 @@ export async function request<T, V extends Record<string, unknown>>( headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: print(document), variables }), }); + if (response.status === 401 || response.redirected) { throw new Error("Your session expired. Please sign in again."); } + if (!response.ok) { throw new Error(`Request failed (${response.status}). Please try again.`); } + const result = (await response.json()) as { data?: T; errors?: { message: string }[] }; + if (result.errors?.length) { throw new Error(result.errors.map((error) => error.message).join(". ")); } + if (!result.data) { throw new Error("The server returned no data."); } + return result.data; } diff --git a/frontend/src/api/operations.graphql b/frontend/src/api/operations.graphql index a1e78f8..ac27f68 100644 --- a/frontend/src/api/operations.graphql +++ b/frontend/src/api/operations.graphql @@ -11,6 +11,7 @@ fragment Booking on Event { created_at updated_at } + query CurrentUser { user { sub @@ -22,6 +23,7 @@ query CurrentUser { is_admin } } + query Calendar($from: String!, $to: String!) { eventsFT(from: $from, to: $to) { ...Booking @@ -34,29 +36,34 @@ query Calendar($from: String!, $to: String!) { room } } + query BookingDetail($id: String!) { event(id: $id) { ...Booking } } + mutation CreateBooking($event: InputEvent!) { createEvent(event: $event) { en sv } } + mutation UpdateBooking($event: InputEvent!) { editEvent(event: $event) { en sv } } + mutation DeleteBooking($id: String!) { deleteEvent(id: $id) { en sv } } + query Rules { rules { id @@ -74,12 +81,14 @@ query Rules { room } } + mutation CreateRule($rule: InputRule!) { createRule(rule: $rule) { en sv } } + mutation DeleteRule($id: String!) { deleteRule(id: $id) { en diff --git a/frontend/src/components/booking-form.tsx b/frontend/src/components/booking-form.tsx index 0d67683..0b643ed 100644 --- a/frontend/src/components/booking-form.tsx +++ b/frontend/src/components/booking-form.tsx @@ -44,42 +44,52 @@ export function BookingForm({ const { t, language } = useLanguage(); const navigate = useNavigate(); const queryClient = useQueryClient(); + const [selected, setSelected] = useState<Room[]>( booking?.room.filter((room): room is Room => Boolean(room)) ?? ["BIG_HUB"], ); + const [terms, setTerms] = useState(Boolean(booking)); const [privacy, setPrivacy] = useState(Boolean(booking)); const [notification, setNotification] = useState(Boolean(booking)); const [error, setError] = useState(""); const [defaultStart] = useState(() => new Date()); const groups = user?.groups?.filter((group): group is string => Boolean(group)) ?? []; + const eligibleGroups = user?.is_admin && booking && !groups.includes(booking.booked_as) ? [...groups, booking.booked_as] : groups; + const start = booking ? localInput(parseDate(booking.start)) : initialStart || localInput(defaultStart); + const end = booking ? localInput(parseDate(booking.end)) : initialEnd || localInput(addHours(defaultStart, 1)); + const mutation = useMutation({ mutationFn: async (form: FormData) => { const from = new Date(formText(form, "start")); const to = new Date(formText(form, "end")); + if (!(to > from)) { throw new Error( t("End time must be after start time.", "Sluttiden måste vara efter starttiden."), ); } + if (!selected.length) { throw new Error(t("Select at least one room.", "Välj minst ett rum.")); } + if (!terms || !privacy || !notification) { throw new Error( t("Please confirm all three booking conditions.", "Bekräfta alla tre bokningsvillkor."), ); } + const event = { ...(booking?.id ? { id: booking.id } : {}), title: formText(form, "title").trim(), @@ -91,11 +101,14 @@ export function BookingForm({ booked_as: formText(form, "booked_as"), booking_terms: terms, }; + if (booking) { const data = await request(UpdateBookingDocument, { event }); + checkMutation(data.editEvent, language); } else { const data = await request(CreateBookingDocument, { event }); + checkMutation(data.createEvent, language); } }, @@ -107,11 +120,13 @@ export function BookingForm({ }, onError: (reason: Error) => setError(reason.message), }); + function submit(event: FormEvent<HTMLFormElement>) { event.preventDefault(); setError(""); mutation.mutate(new FormData(event.currentTarget)); } + if (!eligibleGroups.length) { return ( <p role="alert" className="rounded-lg bg-amber-50 p-5"> @@ -122,6 +137,7 @@ export function BookingForm({ </p> ); } + return ( <form onSubmit={submit} className="space-y-7"> <div className="grid gap-5 sm:grid-cols-2"> diff --git a/frontend/src/components/date-time-fields.tsx b/frontend/src/components/date-time-fields.tsx index 4f7dfd5..5d9aefa 100644 --- a/frontend/src/components/date-time-fields.tsx +++ b/frontend/src/components/date-time-fields.tsx @@ -60,6 +60,7 @@ export function DatePicker({ const { language, t } = useLanguage(); const [open, setOpen] = useState(false); const calendarRef = useRef<HTMLDivElement>(null); + const [value, setValue] = useState<CalendarDate | CalendarDateTime | null>(() => withTime ? parseDateTime(defaultValue) : parseDate(defaultValue), ); @@ -101,6 +102,7 @@ export function DatePicker({ collisionPadding={16} onOpenAutoFocus={(event) => { event.preventDefault(); + calendarRef.current ?.querySelector<HTMLElement>('[role="button"][tabindex="0"]') ?.focus(); @@ -120,6 +122,7 @@ export function DatePicker({ ? new CalendarDateTime(date.year, date.month, date.day) : date, ); + setOpen(false); }} firstDayOfWeek="mon" diff --git a/frontend/src/components/event-calendar.tsx b/frontend/src/components/event-calendar.tsx index 455cbbe..dce2d54 100644 --- a/frontend/src/components/event-calendar.tsx +++ b/frontend/src/components/event-calendar.tsx @@ -10,6 +10,7 @@ import { EventCalendarCloseIcon } from "@/components/event-calendar-icons"; import { cn } from "@/lib/utils"; const plugins = [dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin, multiMonthPlugin]; + const defaultAvailableViews = [ "dayGridMonth", "timeGridWeek", @@ -17,7 +18,9 @@ const defaultAvailableViews = [ "listWeek", "multiMonthYear", ]; + const navLinkDayClick = "timeGridDay"; + const navLinkWeekClick = "timeGridWeek"; export interface EventCalendarProps extends Omit< diff --git a/frontend/src/components/feedback.tsx b/frontend/src/components/feedback.tsx index c793388..f24d782 100644 --- a/frontend/src/components/feedback.tsx +++ b/frontend/src/components/feedback.tsx @@ -1,5 +1,6 @@ import { AlertCircle, LoaderCircle } from "lucide-react"; import { Button } from "@/components/ui/button"; + export function Loading() { return ( <output className="flex justify-center items-center gap-3 p-12 text-muted-foreground"> @@ -8,6 +9,7 @@ export function Loading() { </output> ); } + export function Failure({ error, retry }: { error: Error; retry?: () => void }) { return ( <div role="alert" className="my-6 rounded-xl border border-red-200 bg-red-50 p-6"> diff --git a/frontend/src/components/ui/event-calendar-views.tsx b/frontend/src/components/ui/event-calendar-views.tsx index 95cae43..6b5aff5 100644 --- a/frontend/src/components/ui/event-calendar-views.tsx +++ b/frontend/src/components/ui/event-calendar-views.tsx @@ -5,15 +5,20 @@ import { cn } from "@/lib/utils"; const xxsTextClass = "text-[0.6875rem]/[1.090909]"; const blockPointerResizerClass = "absolute hidden group-hover:block"; + const rowPointerResizerClass = `${blockPointerResizerClass} inset-y-0 w-2`; + const columnPointerResizerClass = `${blockPointerResizerClass} inset-x-0 h-2`; const blockTouchResizerClass = "absolute size-2 border border-(--fc-event-color) bg-background rounded-full"; + const rowTouchResizerClass = `${blockTouchResizerClass} top-1/2 -mt-1`; + const columnTouchResizerClass = `${blockTouchResizerClass} left-1/2 -ml-1`; const tallDayCellBottomClass = "min-h-4"; + const getShortDayCellBottomClass = (info: DayCellInfo) => cn(!info.isNarrow && "min-h-px"); const dayRowCommonClasses: CalendarOptions = { diff --git a/frontend/src/lib/booking-colors.ts b/frontend/src/lib/booking-colors.ts index 20d7317..06021d0 100644 --- a/frontend/src/lib/booking-colors.ts +++ b/frontend/src/lib/booking-colors.ts @@ -6,15 +6,19 @@ export function bookingRoomClass(selected: readonly (Room | null)[]) { (value, room, index) => value | (selected.includes(room.id) ? 1 << index : 0), 0, ); + const multiple = mask !== 0 && (mask & (mask - 1)) !== 0; + return `booking-rooms-${mask}${multiple ? " booking-multi-room" : ""}`; } export const bookingRoomStyles = Array.from({ length: (1 << rooms.length) - 1 }, (_, index) => { const mask = index + 1; const colors = rooms.filter((_, bit) => mask & (1 << bit)).map((room) => room.color); + const stops = colors .map((color, stop) => `${color} ${stop * 25}px ${(stop + 1) * 25}px`) .join(", "); + return `.booking-rooms-${mask} { --booking-stripes: repeating-linear-gradient(45deg, ${stops}); }`; }).join("\n"); diff --git a/frontend/src/lib/calendar-blocks.test.ts b/frontend/src/lib/calendar-blocks.test.ts index 5390446..3787675 100644 --- a/frontend/src/lib/calendar-blocks.test.ts +++ b/frontend/src/lib/calendar-blocks.test.ts @@ -24,6 +24,7 @@ describe("calendar blocked-period labels", () => { const blocks = calendarBlocks([rule, rule, rule]); expect(blocks).toHaveLength(1); + expect(blocks[0]).toMatchObject({ title: "Reading morning", description: "Reading morning — No bookings", @@ -39,6 +40,7 @@ describe("calendar blocked-period labels", () => { expect(blocks).toHaveLength(1); expect(blocks[0]?.title).toBe("Maintenance · Reading"); + expect(blocks[0]?.description).toBe( "Maintenance — Room closed\nReading — Exam preparation\nReading — Quiet time", ); @@ -69,7 +71,9 @@ describe("calendar blocked-period labels", () => { it("handles an empty room selection and does not mutate the source rules", () => { expect(calendarBlocks([])).toEqual([]); + const rule = Object.freeze(slot("A")); + expect(calendarBlocks(Object.freeze([rule, rule]))).toHaveLength(1); expect(rule).toEqual(slot("A")); }); diff --git a/frontend/src/lib/calendar-blocks.ts b/frontend/src/lib/calendar-blocks.ts index 3957ff0..785531c 100644 --- a/frontend/src/lib/calendar-blocks.ts +++ b/frontend/src/lib/calendar-blocks.ts @@ -21,20 +21,24 @@ export function calendarBlocks(slots: readonly BlockedSlot[]): CalendarBlock[] { start: parseDate(slot.start).getTime(), end: parseDate(slot.end).getTime(), })); + const boundaries = [...new Set(intervals.flatMap(({ start, end }) => [start, end]))].sort( (a, b) => a - b, ); + const blocks: CalendarBlock[] = []; for (let index = 1; index < boundaries.length; index++) { const start = boundaries[index - 1]!; const end = boundaries[index]!; const active = intervals.filter((slot) => slot.start <= start && slot.end >= end); + if (!active.length) { continue; } const title = [...new Set(active.map((slot) => slot.title))].sort().join(" · "); + const description = [ ...new Set( active.map((slot) => slot.title + (slot.description ? ` — ${slot.description}` : "")), @@ -42,6 +46,7 @@ export function calendarBlocks(slots: readonly BlockedSlot[]): CalendarBlock[] { ] .sort() .join("\n"); + const previous = blocks.at(-1); if (previous?.end.getTime() === start && previous.description === description) { diff --git a/frontend/src/lib/dates.test.ts b/frontend/src/lib/dates.test.ts index c6a5f1e..5cec8c5 100644 --- a/frontend/src/lib/dates.test.ts +++ b/frontend/src/lib/dates.test.ts @@ -4,14 +4,18 @@ import { localInput, parseDate } from "./dates"; describe("booking dates", () => { it("accepts ISO dates and the legacy milliseconds contract", () => { const date = new Date("2026-10-25T01:30:00.000Z"); + expect(parseDate(date.toISOString()).getTime()).toBe(date.getTime()); expect(parseDate(String(date.getTime())).getTime()).toBe(date.getTime()); }); + it("rejects invalid server values", () => { expect(() => parseDate("not-a-date")).toThrow("Invalid date"); }); + it("formats local form values without accidentally converting them to UTC", () => { const local = new Date(2026, 8, 6, 14, 30); + expect(localInput(local)).toBe("2026-09-06T14:30"); expect(new Date(localInput(local)).getTime()).toBe(local.getTime()); }); diff --git a/frontend/src/lib/dates.ts b/frontend/src/lib/dates.ts index 797eca1..0b4648d 100644 --- a/frontend/src/lib/dates.ts +++ b/frontend/src/lib/dates.ts @@ -1,11 +1,15 @@ import { format, isValid } from "date-fns"; + export function parseDate(value: string): Date { const date = /^\d+$/.test(value) ? new Date(Number(value)) : new Date(value); + if (!isValid(date)) { throw new Error("Invalid date received from the server."); } + return date; } + export function localInput(value: Date) { return format(value, "yyyy-MM-dd'T'HH:mm"); } diff --git a/frontend/src/lib/forms.ts b/frontend/src/lib/forms.ts index 74647f8..679cdde 100644 --- a/frontend/src/lib/forms.ts +++ b/frontend/src/lib/forms.ts @@ -1,4 +1,5 @@ export function formText(form: FormData, name: string): string { const value = form.get(name); + return typeof value === "string" ? value : ""; } diff --git a/frontend/src/lib/language.tsx b/frontend/src/lib/language.tsx index 53efdb1..d0ba63f 100644 --- a/frontend/src/lib/language.tsx +++ b/frontend/src/lib/language.tsx @@ -1,23 +1,31 @@ import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from "react"; + type Language = "en" | "sv"; + const LanguageContext = createContext<{ language: Language; setLanguage: (language: Language) => void; }>({ language: "en", setLanguage: () => undefined }); + export function LanguageProvider({ children }: { children: ReactNode }) { const [language, update] = useState<Language>(() => localStorage.getItem("bookit-language") === "sv" ? "sv" : "en", ); + useEffect(() => { document.documentElement.lang = language; }, [language]); + const setLanguage = useCallback((value: Language) => { localStorage.setItem("bookit-language", value); update(value); }, []); + return <LanguageContext value={{ language, setLanguage }}>{children}</LanguageContext>; } + export function useLanguage() { const context = useContext(LanguageContext); + return { ...context, t: (en: string, sv: string) => (context.language === "sv" ? sv : en) }; } diff --git a/frontend/src/lib/rooms.ts b/frontend/src/lib/rooms.ts index cb9dfdd..0d4f33d 100644 --- a/frontend/src/lib/rooms.ts +++ b/frontend/src/lib/rooms.ts @@ -1,4 +1,5 @@ import type { Room } from "@/generated/graphql"; + export const rooms: { id: Room; name: string; color: string }[] = [ { id: "BIG_HUB", @@ -13,6 +14,7 @@ export const rooms: { id: Room; name: string; color: string }[] = [ { id: "CTC", name: "CTC", color: "#A63446" }, { id: "THE_CLOUD", name: "The Cloud", color: "#3F784C" }, ]; + export function roomName(id: Room) { return rooms.find((room) => room.id === id)?.name ?? id; } diff --git a/frontend/src/lib/user.ts b/frontend/src/lib/user.ts index 2513f94..ecf76e9 100644 --- a/frontend/src/lib/user.ts +++ b/frontend/src/lib/user.ts @@ -1,18 +1,22 @@ import { queryOptions, useQuery } from "@tanstack/react-query"; import { request } from "@/api/client"; import { CurrentUserDocument } from "@/generated/graphql"; + export const userOptions = queryOptions({ queryKey: ["user"], queryFn: async () => { const { user } = await request(CurrentUserDocument, {}); + if (!user) { throw new Error("Please sign in to book a room."); } + return user; }, staleTime: 60_000, retry: false, }); + export function useUser() { return useQuery(userOptions); } diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts index 109c4e2..a5ef193 100644 --- a/frontend/src/lib/utils.ts +++ b/frontend/src/lib/utils.ts @@ -1,5 +1,6 @@ import { clsx, type ClassValue } from "clsx"; import { twMerge } from "tailwind-merge"; + export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 8cc0c80..0318a8f 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -6,11 +6,15 @@ import { Toaster } from "sonner"; import { router } from "./router"; import { LanguageProvider } from "./lib/language"; import "./styles.css"; + const client = new QueryClient({ defaultOptions: { queries: { retry: 1, staleTime: 20_000 } } }); + const root = document.getElementById("root"); + if (!root) { throw new Error("Application root is missing"); } + createRoot(root).render( <StrictMode> <QueryClientProvider client={client}> diff --git a/frontend/src/pages/booking.tsx b/frontend/src/pages/booking.tsx index 9354d4d..c8a0d7a 100644 --- a/frontend/src/pages/booking.tsx +++ b/frontend/src/pages/booking.tsx @@ -26,6 +26,7 @@ import { Failure, Loading } from "@/components/feedback"; export function NewBookingPage() { const { t } = useLanguage(); const { start, end } = useSearch({ from: "/new-event" }); + return ( <div className="max-w-3xl mx-auto"> <Link to="/" className="flex gap-2 items-center text-sm text-muted-foreground mb-6"> @@ -41,11 +42,14 @@ export function NewBookingPage() { </div> ); } + export function BookingPage() { const { id } = useParams({ from: "/bookings/$id" }); const { edit } = useSearch({ from: "/bookings/$id" }); + return <BookingDetails id={id} initialEditing={edit} />; } + export function BookingDetails({ id, initialEditing = false, @@ -61,13 +65,16 @@ export function BookingDetails({ const [deleting, setDeleting] = useState(false); const navigate = useNavigate(); const cache = useQueryClient(); + const query = useQuery({ queryKey: ["booking", id], queryFn: () => request(BookingDetailDocument, { id }), }); + const mutation = useMutation({ mutationFn: async () => { const result = await request(DeleteBookingDocument, { id }); + checkMutation(result.deleteEvent, language); }, onSuccess: async () => { @@ -78,17 +85,23 @@ export function BookingDetails({ }, onError: (error) => toast.error(error.message), }); + if (query.isPending) { return <Loading />; } + if (query.error) { return <Failure error={query.error} />; } + const booking = query.data.event; + if (!booking) { return <p role="alert">{t("Booking not found.", "Bokningen hittades inte.")}</p>; } + const canEdit = user?.is_admin || user?.groups?.includes(booking.booked_as); + return ( <div className="max-w-3xl mx-auto"> {!onClose && ( diff --git a/frontend/src/pages/calendar.tsx b/frontend/src/pages/calendar.tsx index f60ff1a..c590647 100644 --- a/frontend/src/pages/calendar.tsx +++ b/frontend/src/pages/calendar.tsx @@ -43,30 +43,40 @@ export function CalendarPage() { const [detailId, setDetailId] = useState<string | null>(null); const [pendingMove, setPendingMove] = useState<BookingMove | null>(null); const savingMove = useRef(false); + const [viewport, setViewport] = useState(() => ({ width: window.innerWidth, height: window.innerHeight, })); + useEffect(() => { const resize = () => setViewport({ width: window.innerWidth, height: window.innerHeight }); + window.addEventListener("resize", resize); + return () => window.removeEventListener("resize", resize); }, []); + const [range, setRange] = useState(() => ({ from: startOfWeek(new Date(), { weekStartsOn: 1 }).toISOString(), to: addDays(startOfWeek(new Date(), { weekStartsOn: 1 }), 7).toISOString(), })); + const [selectedRooms, setSelectedRooms] = useState<Room[]>(rooms.map((room) => room.id)); + const query = useQuery({ queryKey: ["calendar", range], queryFn: () => request(CalendarDocument, range), }); + const bookings = (query.data?.eventsFT ?? []).filter((event): event is BookingFragment => Boolean(event), ); + const visible = bookings.filter((event) => event.room.some((room) => room && selectedRooms.includes(room)), ); + const move = useMutation({ mutationFn: async ({ booking, start, end }: BookingMove) => { const { editEvent } = await request(UpdateBookingDocument, { @@ -81,6 +91,7 @@ export function CalendarPage() { booking_terms: true, }, }); + checkMutation(editEvent, language); }, onSuccess: async (_result, { booking }) => { @@ -88,6 +99,7 @@ export function CalendarPage() { cache.invalidateQueries({ queryKey: ["calendar"] }), cache.invalidateQueries({ queryKey: ["booking", booking.id] }), ]); + toast.success(t("Booking moved", "Bokningen flyttades")); }, }); @@ -102,7 +114,9 @@ export function CalendarPage() { if (!pendingMove || savingMove.current) { return; } + savingMove.current = true; + try { await move.mutateAsync(pendingMove); } catch (error) { @@ -123,6 +137,7 @@ export function CalendarPage() { <div className="flex flex-wrap gap-2" aria-label={t("Room filters", "Rumsfilter")}> {rooms.map((room) => { const active = selectedRooms.includes(room.id); + return ( <Button key={room.id} @@ -202,7 +217,9 @@ export function CalendarPage() { const booking = bookings.find((event) => event.id === info.event.id); const start = info.event.start; const end = info.event.end; + info.revert(); + if ( pendingMove || savingMove.current || @@ -213,6 +230,7 @@ export function CalendarPage() { ) { return; } + setPendingMove({ booking, start, end }); }} diff --git a/frontend/src/pages/rules.tsx b/frontend/src/pages/rules.tsx index b128d6e..90fbbad 100644 --- a/frontend/src/pages/rules.tsx +++ b/frontend/src/pages/rules.tsx @@ -43,6 +43,7 @@ import { import { Failure, Loading } from "@/components/feedback"; type RuleDetails = NonNullable<NonNullable<RulesQuery["rules"]>[number]>; + type SortKey = | "title" | "priority" @@ -57,12 +58,15 @@ function sortValue(rule: RuleDetails, key: SortKey): string | number { if (key === "priority" || key === "day_mask") { return rule[key] ?? 0; } + if (key === "allow") { return Number(rule.allow); } + if (key === "room") { return rule.room?.map(roomName).sort().join(", ") ?? ""; } + return rule[key] ?? ""; } @@ -79,26 +83,33 @@ export function RulesPage() { const [ascending, setAscending] = useState(true); const [pageIndex, setPageIndex] = useState(0); const [error, setError] = useState(""); + const weekdays = language === "en" ? ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] : ["Mån", "Tis", "Ons", "Tor", "Fre", "Lör", "Sön"]; + const query = useQuery({ queryKey: ["rules"], queryFn: () => request(RulesDocument, {}) }); + const sorted = (query.data?.rules ?? []) .filter((rule): rule is RuleDetails => rule !== null) .sort((a, b) => { const left = sortValue(a, sortKey); const right = sortValue(b, sortKey); + const result = typeof left === "number" && typeof right === "number" ? left - right : String(left).localeCompare(String(right), language, { sensitivity: "base" }); + return ascending ? result : -result; }); + const pageCount = Math.max(1, Math.ceil(sorted.length / 10)); const currentPage = Math.min(pageIndex, pageCount - 1); const pageRules = sorted.slice(currentPage * 10, currentPage * 10 + 10); const details = sorted.find((rule) => rule.id === detailsId); + const columns: { key: SortKey; label: string }[] = [ { key: "title", label: t("Title", "Titel") }, { key: "priority", label: t("Priority", "Prioritet") }, @@ -109,14 +120,18 @@ export function RulesPage() { { key: "day_mask", label: t("Weekdays", "Veckodagar") }, { key: "allow", label: t("Availability", "Tillgänglighet") }, ]; + const dateText = (value: string | null | undefined, withTime = false) => value ? format(parseDate(value), withTime ? "d MMM yyyy, HH:mm" : "d MMM yyyy") : "—"; + const dayText = (mask: number | null | undefined) => weekdays.filter((_, index) => ((mask ?? 0) & (1 << index)) !== 0).join(" · ") || "—"; + async function invalidate() { await cache.invalidateQueries({ queryKey: ["rules"] }); await cache.invalidateQueries({ queryKey: ["calendar"] }); } + const create = useMutation({ mutationFn: async (form: FormData) => { if (!selected.length || !days) { @@ -124,6 +139,7 @@ export function RulesPage() { t("Select rooms and at least one weekday.", "Välj rum och minst en veckodag."), ); } + const rule = { title: formText(form, "title").trim(), description: formText(form, "description"), @@ -136,12 +152,15 @@ export function RulesPage() { start_time: formText(form, "start_time").slice(0, 5), end_time: formText(form, "end_time").slice(0, 5), }; + if (rule.end_date < rule.start_date || rule.end_time <= rule.start_time) { throw new Error( t("The rule must end after it starts.", "Regeln måste sluta efter att den börjar."), ); } + const result = await request(CreateRuleDocument, { rule }); + checkMutation(result.createRule, language); }, onSuccess: async () => { @@ -152,9 +171,11 @@ export function RulesPage() { }, onError: (reason) => setError(reason.message), }); + const remove = useMutation({ mutationFn: async (id: string) => { const result = await request(DeleteRuleDocument, { id }); + checkMutation(result.deleteRule, language); }, onSuccess: async () => { @@ -164,11 +185,13 @@ export function RulesPage() { }, onError: (reason) => toast.error(reason.message), }); + function submit(event: FormEvent<HTMLFormElement>) { event.preventDefault(); setError(""); create.mutate(new FormData(event.currentTarget)); } + return ( <> <div className="flex justify-between items-end flex-wrap gap-5 mb-7"> diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx index 4d25df0..064259d 100644 --- a/frontend/src/router.tsx +++ b/frontend/src/router.tsx @@ -17,11 +17,13 @@ import { Failure, Loading } from "@/components/feedback"; function Layout() { const user = useUser(); const { t, language, setLanguage } = useLanguage(); + useEffect(() => { if (user.data?.locale && !localStorage.getItem("bookit-language")) { setLanguage(user.data.locale.startsWith("sv") ? "sv" : "en"); } }, [user.data?.locale, setLanguage]); + return ( <> <a @@ -92,6 +94,7 @@ function Layout() { </> ); } + const rootRoute = createRootRoute({ component: Layout, notFoundComponent: () => ( @@ -103,11 +106,13 @@ const rootRoute = createRootRoute({ ), errorComponent: ({ error, reset }) => <Failure error={error} retry={reset} />, }); + const homeRoute = createRoute({ getParentRoute: () => rootRoute, path: "/", component: lazyRouteComponent(() => import("@/pages/calendar"), "CalendarPage"), }); + const newRoute = createRoute({ getParentRoute: () => rootRoute, path: "/new-event", @@ -123,6 +128,7 @@ const newRoute = createRoute({ }), component: lazyRouteComponent(() => import("@/pages/booking"), "NewBookingPage"), }); + const detailRoute = createRoute({ getParentRoute: () => rootRoute, path: "/bookings/$id", @@ -131,11 +137,13 @@ const detailRoute = createRoute({ }), component: lazyRouteComponent(() => import("@/pages/booking"), "BookingPage"), }); + const rulesRoute = createRoute({ getParentRoute: () => rootRoute, path: "/rules", component: lazyRouteComponent(() => import("@/pages/rules"), "RulesPage"), }); + const legacyEditRoute = createRoute({ getParentRoute: () => rootRoute, path: "/edit-event", @@ -151,13 +159,16 @@ const legacyEditRoute = createRoute({ replace: true, }); } + throw redirect({ to: "/", replace: true }); }, }); + export const router = createRouter({ scrollRestoration: true, routeTree: rootRoute.addChildren([homeRoute, newRoute, detailRoute, rulesRoute, legacyEditRoute]), }); + declare module "@tanstack/react-router" { interface Register { router: typeof router; diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 015f496..8ee50d5 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -27,6 +27,7 @@ --radius-lg: 0.75rem; --radius-xl: 1rem; } + :root { --background: #f6f8fb; --foreground: #17283a; @@ -39,15 +40,18 @@ color: var(--foreground); background: var(--background); } + @layer base { * { border-color: var(--color-border); } } + body { margin: 0; min-width: 320px; } + button, a, input, @@ -55,10 +59,12 @@ select, textarea { -webkit-tap-highlight-color: transparent; } + button:not(:disabled), [role="button"] { cursor: pointer; } + a, button, input, @@ -66,11 +72,13 @@ select, textarea { outline-offset: 4px; } + .app-shell { max-width: 1560px; margin: auto; padding: 2.5rem 3rem 4rem; } + .eyebrow { font-size: 0.7rem; font-weight: 700; @@ -78,16 +86,19 @@ textarea { text-transform: uppercase; color: #697a8d; } + .page-title { font-size: clamp(1.8rem, 3vw, 2.6rem); line-height: 1.2; letter-spacing: -0.045em; font-weight: 700; } + .field { display: grid; gap: 0.55rem; } + .native-select { min-height: 2.5rem; width: 100%; @@ -97,16 +108,19 @@ textarea { font-size: 0.875rem; background: white; } + .booking-multi-room { background-image: var(--booking-stripes); color: white; } + .booking-multi-room .booking-event-label > * { background: rgb(0 0 0 / 55%); border-radius: 2px; color: white; align-self: flex-start; } + @media (max-width: 760px) { .app-shell { padding: 1.5rem 1rem; diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 2b889a0..7022474 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -2,6 +2,7 @@ import { fileURLToPath, URL } from "node:url"; import { defineConfig } from "vite-plus"; import react from "@vitejs/plugin-react"; import tailwindcss from "@tailwindcss/vite"; + export default defineConfig({ plugins: [react(), tailwindcss()], resolve: { alias: { "@": fileURLToPath(new URL("./src", import.meta.url)) } }, diff --git a/playwright.config.ts b/playwright.config.ts index 0bb42e2..2783bc5 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,4 +1,5 @@ import { defineConfig, devices } from "@playwright/test"; + export default defineConfig({ testDir: "./e2e", testMatch: "**/*.spec.ts", diff --git a/vite.config.ts b/vite.config.ts index ec6a26f..53d77f1 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,4 +1,5 @@ import { defineConfig } from "vite-plus"; + export default defineConfig({ test: { include: ["backend/src/**/*.test.ts", "frontend/src/**/*.test.ts"], @@ -6,8 +7,16 @@ export default defineConfig({ }, lint: { plugins: ["typescript", "react", "jsx-a11y", "import", "unicorn", "oxc"], - rules: { curly: ["error", "all"] }, + rules: { + curly: ["error", "all"], + "import/newline-after-import": "error", + }, overrides: [ + { + files: ["**/*.test.ts"], + plugins: ["vitest"], + rules: { "vitest/padding-around-test-blocks": "error" }, + }, { files: ["frontend/src/**/*.ts", "frontend/src/**/*.tsx"], rules: { "react/rules-of-hooks": "error", "react/exhaustive-deps": "warn" }, From 814dc03decc8b0c6e6eb78c5c71be70ce5553ac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 13:43:03 +0200 Subject: [PATCH 30/41] Use local Gamma for development supporting services --- README.md | 19 ++++++--- backend/.env.example | 10 ++--- backend/package.json | 2 +- backend/src/index.ts | 16 +++++++ docker-compose.yml | 93 +++++++++++++++++++++++++++++++++++++---- e2e/compose.ts | 1 + gamma/bookit-client.sql | 51 ++++++++++++++++++++++ gamma/users.json | 60 ++++++++++++++++++++++++++ prod.docker-compose.yml | 59 -------------------------- 9 files changed, 231 insertions(+), 80 deletions(-) create mode 100644 gamma/bookit-client.sql create mode 100644 gamma/users.json delete mode 100644 prod.docker-compose.yml diff --git a/README.md b/README.md index a158785..1719079 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,6 @@ A booking service for the Chalmers Software Engineering Student Division (IT) - [Docker](https://www.docker.com/) - [Node.js](https://nodejs.org/en/) 24 (see `.node-version`) - pnpm 12.3.4 -- A Gamma OAuth client and CLIENT API key for local authentication ## Setup @@ -25,13 +24,15 @@ corepack prepare pnpm@12.3.4 --activate pnpm install --frozen-lockfile pnpm codegen pnpm --dir backend exec prisma generate -docker compose up -d db redis -cp backend/.env.example backend/.env +docker compose up -d --wait db redis gamma +docker compose run --rm gamma-init +test -f backend/.env || cp backend/.env.example backend/.env ``` -Configure Gamma's issuer URL, client credentials and API key in `backend/.env`. -Register `http://localhost:8080/api/callback` as the client's redirect URL and -set `SESSION_SECRET` using `openssl rand -hex 32`. +Compose starts local Gamma, its test users and BookIT OAuth client, and separate +PostgreSQL/Redis instances for each app. Copy the local authentication values from +`backend/.env.example` if you already have an `.env`, and set `SESSION_SECRET` +using `openssl rand -hex 32`. No production credentials are needed. Backend, after confirming `DATABASE_URL` points to your local database: @@ -47,8 +48,14 @@ pnpm --dir frontend dev ``` Open [http://localhost:8080](http://localhost:8080) to view the website. +Sign in with `bookmember` (digIT), `bookadmin` (BookIT admin), or `bookguest` +(no group), all with password `password1337`. Gamma runs at `http://localhost:8081`. The GraphQL endpoint is `/api/graphql/v1`. +Ordinary startup preserves database contents; do not use `docker compose down -v` +to stop development. Use `docker compose stop` instead. Compose is development-only; +deployments must provide their own authentication settings with `NODE_ENV=production`. + Run `pnpm check`, `pnpm test` and `pnpm build` for the project checks. See [browser tests](e2e/README.md) for Playwright setup and CI image testing. diff --git a/backend/.env.example b/backend/.env.example index 7e70eb1..db4b1c9 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -6,13 +6,13 @@ DB_NAME=bookit DATABASE_URL=postgresql://postgres:password@127.0.0.1:5432/bookit -ISSUER_BASE_URL=https://auth.chalmers.it +ISSUER_BASE_URL=http://localhost:8081 BASE_URL=http://localhost:8080 -CLIENT_ID=replace-with-gamma-client-id -CLIENT_SECRET=replace-with-gamma-client-secret -API_KEY=replace-with-client-api-key-id:replace-with-client-api-key +CLIENT_ID=BOOKITLOCALDEVELOPMENTCLIENT01 +CLIENT_SECRET=password1337 +API_KEY=9c48b6ac-3b70-4c50-b171-b41eb111af67:password1337 -FRONTEND_URL=http://localhost:3001 # URL to the frontend used for proxying +FRONTEND_URL=http://localhost:3001 REDIS_HOST=localhost REDIS_PORT=6379 diff --git a/backend/package.json b/backend/package.json index 4a54b39..f5a321d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -13,7 +13,7 @@ "main": "index.js", "scripts": { "start": "tsx src/index.ts", - "dev": "tsx watch --env-file=.env src/index.ts", + "dev": "NODE_ENV=development tsx watch --env-file=.env src/index.ts", "migrate": "prisma db push", "test": "vp test run", "typecheck": "tsc --noEmit", diff --git a/backend/src/index.ts b/backend/src/index.ts index a33a774..cf690e0 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -26,6 +26,18 @@ const requiredEnvironment = (name: string): string => { async function main() { process.env.TZ ??= "Europe/Stockholm"; + const issuerBaseURL = requiredEnvironment("ISSUER_BASE_URL"); + const issuer = new URL(issuerBaseURL); + const localIssuer = + ["localhost", "127.0.0.1", "[::1]"].includes(issuer.hostname) || + issuer.hostname.endsWith(".localhost"); + + if ((process.env.NODE_ENV !== "production" || process.env.CI) && !localIssuer) { + throw new Error("Development and CI require a local Gamma ISSUER_BASE_URL"); + } + + requiredEnvironment("API_KEY"); + const app = express(); app.disable("x-powered-by"); @@ -58,6 +70,10 @@ async function main() { app.use( auth({ + issuerBaseURL, + baseURL: requiredEnvironment("BASE_URL"), + clientID: requiredEnvironment("CLIENT_ID"), + clientSecret: requiredEnvironment("CLIENT_SECRET"), secret: requiredEnvironment("SESSION_SECRET"), idpLogout: true, authRequired: true, diff --git a/docker-compose.yml b/docker-compose.yml index eb531a0..ed13881 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,19 +8,94 @@ services: TZ: "Europe/Stockholm" ports: - 127.0.0.1:5432:5432 + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres -d bookit"] + interval: 5s + timeout: 5s + retries: 20 redis: image: redis:5.0.14-alpine ports: - 127.0.0.1:6379:6379 + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 5s + retries: 20 - db-scripts: # Deletes personal data from database - build: - context: ./db-scripts - dockerfile: Dockerfile + gamma-db: + image: postgres:16.0-alpine environment: - PGPASSWORD: password - DB_USER: postgres - DB_HOST: db - DB_PORT: 5432 - DB_NAME: bookit + POSTGRES_DB: gamma + POSTGRES_USER: gamma + POSTGRES_PASSWORD: gamma-local + volumes: + - gamma-db:/var/lib/postgresql/data + healthcheck: + test: ["CMD", "pg_isready", "-U", "gamma", "-d", "gamma"] + interval: 5s + timeout: 5s + retries: 20 + + gamma-redis: + image: redis:5.0.14-alpine + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 5s + retries: 20 + + gamma: + image: ghcr.io/cthit/gamma:2.5.1@sha256:5112c5673ee5c98b072c38afe4ed0de5f7f7d6ae85c41bbd318c92f0e3d5d9db + platform: linux/amd64 + depends_on: + gamma-db: + condition: service_healthy + gamma-redis: + condition: service_healthy + environment: + DB_HOST: gamma-db + DB_NAME: gamma + DB_USER: gamma + DB_PASSWORD: gamma-local + REDIS_HOST: gamma-redis + SERVER_PORT: "8081" + BASE_URL: http://localhost:8081 + PRODUCTION: "false" + IS_MOCKING: "true" + ADMIN_SETUP: "true" + MOCK_DATA_RESOURCE: file:/tmp/bookit-users.json + UPLOAD_FOLDER: /tmp/uploads/ + ports: + - 127.0.0.1:8081:8081 + volumes: + - ./gamma/users.json:/tmp/bookit-users.json:ro + + gamma-init: + image: postgres:16.0-alpine + depends_on: + gamma: + condition: service_started + environment: + PGHOST: gamma-db + PGDATABASE: gamma + PGUSER: gamma + PGPASSWORD: gamma-local + volumes: + - ./gamma/bookit-client.sql:/init/bookit-client.sql:ro + entrypoint: ["/bin/sh", "-ec"] + command: + - | + for attempt in $$(seq 1 120); do + if wget -q -T 2 -O /dev/null http://gamma:8081/login && + [ "$$(psql -Atc "SELECT count(*) FROM g_user WHERE cid IN ('bookadmin', 'bookmember', 'bookguest')" 2>/dev/null)" = 3 ]; then + exec psql -v ON_ERROR_STOP=1 -f /init/bookit-client.sql + fi + sleep 2 + done + echo "Gamma did not become ready with its local test users" >&2 + exit 1 + +volumes: + gamma-db: diff --git a/e2e/compose.ts b/e2e/compose.ts index 161315a..0bd2756 100644 --- a/e2e/compose.ts +++ b/e2e/compose.ts @@ -311,6 +311,7 @@ export async function compose(browser: Browser): Promise<Environment> { const env = { NODE_ENV: "production", + CI: "true", TZ: "Europe/Stockholm", DATABASE_URL: "postgresql://bookit_test:bookit_test@bookit-db:5432/bookit_test", REDIS_HOST: "bookit-redis", diff --git a/gamma/bookit-client.sql b/gamma/bookit-client.sql new file mode 100644 index 0000000..7a0f660 --- /dev/null +++ b/gamma/bookit-client.sql @@ -0,0 +1,51 @@ +BEGIN; + +INSERT INTO g_text (text_id, sv, en) +VALUES ('144401ae-01a7-4dce-b6ad-03d63173f8d8', 'BookIT lokal utveckling', 'BookIT local development') +ON CONFLICT DO NOTHING; + +-- Local-only credentials use the mock user's password: password1337. +INSERT INTO g_client ( + client_uid, client_id, client_secret, redirect_uri, pretty_name, + created_at, description, official +) +VALUES ( + 'b2753bd1-9ca2-42e7-b0f2-28dd56bcc15d', 'BOOKITLOCALDEVELOPMENTCLIENT01', + (SELECT password FROM g_user WHERE cid = 'bookmember'), + 'http://localhost:8080/api/callback', 'BookIT local', + CURRENT_TIMESTAMP, '144401ae-01a7-4dce-b6ad-03d63173f8d8', true +) +ON CONFLICT DO NOTHING; + +INSERT INTO g_client_scope (client_uid, scope, created_at) +VALUES ('b2753bd1-9ca2-42e7-b0f2-28dd56bcc15d', 'PROFILE', CURRENT_TIMESTAMP) +ON CONFLICT DO NOTHING; + +INSERT INTO g_api_key ( + api_key_id, pretty_name, token, key_type, created_at, updated_at, version, description +) +VALUES ( + '9c48b6ac-3b70-4c50-b171-b41eb111af67', 'BookIT local', + (SELECT password FROM g_user WHERE cid = 'bookmember'), + 'CLIENT', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0, '144401ae-01a7-4dce-b6ad-03d63173f8d8' +) +ON CONFLICT DO NOTHING; + +INSERT INTO g_client_api_key (created_at, client_uid, api_key_id) +VALUES ( + CURRENT_TIMESTAMP, 'b2753bd1-9ca2-42e7-b0f2-28dd56bcc15d', '9c48b6ac-3b70-4c50-b171-b41eb111af67' +) +ON CONFLICT DO NOTHING; + +INSERT INTO g_client_authority (created_at, client_uid, authority_name) +VALUES (CURRENT_TIMESTAMP, 'b2753bd1-9ca2-42e7-b0f2-28dd56bcc15d', 'admin') +ON CONFLICT DO NOTHING; + +INSERT INTO g_client_authority_user (created_at, user_id, client_uid, authority_name) +VALUES ( + CURRENT_TIMESTAMP, '88eec5c2-5ebb-4e13-9a76-fcc4dac9e74f', + 'b2753bd1-9ca2-42e7-b0f2-28dd56bcc15d', 'admin' +) +ON CONFLICT DO NOTHING; + +COMMIT; diff --git a/gamma/users.json b/gamma/users.json new file mode 100644 index 0000000..9b2c707 --- /dev/null +++ b/gamma/users.json @@ -0,0 +1,60 @@ +{ + "users": [ + { + "id": "88eec5c2-5ebb-4e13-9a76-fcc4dac9e74f", + "cid": "bookadmin", + "nick": "BookIT Admin", + "firstName": "BookIT", + "lastName": "Admin", + "acceptanceYear": 2020 + }, + { + "id": "bc605869-9a4d-46ec-8a29-d00819d4c195", + "cid": "bookmember", + "nick": "BookIT Member", + "firstName": "BookIT", + "lastName": "Member", + "acceptanceYear": 2020 + }, + { + "id": "ec8987d7-4087-461d-bed5-9365086b6e3b", + "cid": "bookguest", + "nick": "BookIT Guest", + "firstName": "BookIT", + "lastName": "Guest", + "acceptanceYear": 2020 + } + ], + "superGroups": [ + { + "id": "aed27030-ad90-4526-855c-1e909b1dcecb", + "name": "digit", + "prettyName": "digIT", + "type": "COMMITTEE" + } + ], + "groups": [ + { + "id": "acd27030-ad90-4526-855c-1e909b1dcecb", + "name": "digit-local", + "prettyName": "digIT local", + "superGroupId": "aed27030-ad90-4526-855c-1e909b1dcecb", + "members": [ + { + "userId": "88eec5c2-5ebb-4e13-9a76-fcc4dac9e74f", + "postId": "7bb1db15-730d-4864-bfc3-99abe7c0ccf8" + }, + { + "userId": "bc605869-9a4d-46ec-8a29-d00819d4c195", + "postId": "7bb1db15-730d-4864-bfc3-99abe7c0ccf8" + } + ] + } + ], + "posts": [ + { + "id": "7bb1db15-730d-4864-bfc3-99abe7c0ccf8", + "postName": { "sv": "Testmedlem", "en": "Test member" } + } + ] +} diff --git a/prod.docker-compose.yml b/prod.docker-compose.yml deleted file mode 100644 index edaad32..0000000 --- a/prod.docker-compose.yml +++ /dev/null @@ -1,59 +0,0 @@ -services: - backend: - build: - context: . - dockerfile: backend/Dockerfile - ports: - - "127.0.0.1:8080:8080" - depends_on: - db: - condition: service_healthy - redis: - condition: service_healthy - frontend: - condition: service_started - environment: - DATABASE_URL: "${DATABASE_URL:?Set DATABASE_URL for the BookIT database}" - ISSUER_BASE_URL: "${ISSUER_BASE_URL:?Set the Gamma OpenID issuer URL}" - BASE_URL: "${BASE_URL:?Set the public BookIT URL}" - CLIENT_ID: "${CLIENT_ID:?Set the Gamma client ID}" - CLIENT_SECRET: "${CLIENT_SECRET:?Set the Gamma client secret}" - API_KEY: "${API_KEY:?Set the Gamma client API key}" - SESSION_SECRET: "${SESSION_SECRET:?Set a strong random session secret}" - FRONTEND_URL: http://frontend:80 - REDIS_HOST: redis - REDIS_PORT: "6379" - PORT: "8080" - TZ: Europe/Stockholm - TRUST_PROXY: "${TRUST_PROXY:-0}" - - frontend: - build: - context: . - dockerfile: frontend/Dockerfile - - db: - image: postgres:12.22-alpine - environment: - POSTGRES_DB: bookit - POSTGRES_USER: postgres - POSTGRES_PASSWORD: "${DB_PASS:?Set the BookIT database password}" - TZ: Europe/Stockholm - volumes: - - bookit-db:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres -d bookit"] - interval: 5s - timeout: 5s - retries: 12 - - redis: - image: redis:5.0.14-alpine - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 5s - timeout: 5s - retries: 12 - -volumes: - bookit-db: From 03fc21342fafb74a25f9f54ba273b27cedb3efac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 14:47:29 +0200 Subject: [PATCH 31/41] Handle expired sessions and preserve legacy OIDC secret configuration --- README.md | 2 ++ backend/src/index.ts | 20 ++++++++++++++++++-- e2e/compose.ts | 2 +- e2e/permissions.spec.ts | 32 ++++++++++++++++++++++++++++++++ frontend/src/api/client.ts | 7 +++++++ 5 files changed, 60 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1719079..6bd8218 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,8 @@ The GraphQL endpoint is `/api/graphql/v1`. Ordinary startup preserves database contents; do not use `docker compose down -v` to stop development. Use `docker compose stop` instead. Compose is development-only; deployments must provide their own authentication settings with `NODE_ENV=production`. +Existing deployments can keep `SECRET` for OIDC; `SESSION_SECRET` is used if `SECRET` is absent. +Deploy matching frontend/backend versions together; open tabs may need a refresh and a new sign-in. Run `pnpm check`, `pnpm test` and `pnpm build` for the project checks. See [browser tests](e2e/README.md) for Playwright setup and CI image testing. diff --git a/backend/src/index.ts b/backend/src/index.ts index cf690e0..4bbad5c 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -74,9 +74,9 @@ async function main() { baseURL: requiredEnvironment("BASE_URL"), clientID: requiredEnvironment("CLIENT_ID"), clientSecret: requiredEnvironment("CLIENT_SECRET"), - secret: requiredEnvironment("SESSION_SECRET"), + secret: process.env.SECRET || requiredEnvironment("SESSION_SECRET"), idpLogout: true, - authRequired: true, + authRequired: false, authorizationParams: { scope: "openid profile", response_type: "code" }, clientAuthMethod: "client_secret_basic", routes: { callback: "/api/callback", login: "/api/login", logout: "/api/logout" }, @@ -112,6 +112,22 @@ async function main() { }), ); + app.use(async (req, res, next) => { + if (req.oidc.isAuthenticated()) { + next(); + + return; + } + + if (req.path.startsWith("/api/")) { + res.status(401).json({ error: "Authentication required" }); + + return; + } + + await res.oidc.login({ returnTo: req.originalUrl }); + }); + await Promise.all([redis.connect(), prisma.$connect()]); const apollo = await setupRoutes(app, { prisma }, httpServer); diff --git a/e2e/compose.ts b/e2e/compose.ts index 0bd2756..42c2473 100644 --- a/e2e/compose.ts +++ b/e2e/compose.ts @@ -317,7 +317,7 @@ export async function compose(browser: Browser): Promise<Environment> { REDIS_HOST: "bookit-redis", REDIS_PORT: "6379", REDIS_PASS: "", - SESSION_SECRET: "bookit-isolated-e2e-session-secret", + SESSION_SECRET: "secret", SECRET: "bookit-isolated-e2e-oidc-secret-at-least-32-characters", BASE_URL: appUrl, BACKEND_URL: appUrl, diff --git a/e2e/permissions.spec.ts b/e2e/permissions.spec.ts index 57c81c5..dd96156 100644 --- a/e2e/permissions.spec.ts +++ b/e2e/permissions.spec.ts @@ -1,6 +1,38 @@ import { test, expect, loginAs } from "./fixtures"; import { createBooking, openBooking } from "./booking-helpers"; +test("an expired session navigates to sign-in and returns to the calendar", async ({ + page, + environment, +}) => { + await createBooking(page, "E2E session expiry booking"); + await page.context().clearCookies(); + await page.getByRole("button", { name: /next/i }).click(); + + await expect(page).toHaveURL( + (url) => url.origin === environment.gammaUrl && url.pathname === "/login", + ); + await page.locator('[name="username"]').fill("bookmember"); + await page.locator('[name="password"]').fill("password1337"); + await page.getByRole("button", { name: "Login", exact: true }).click(); + + await expect(page).toHaveURL(`${environment.appUrl}/`); + await expect(page.getByText("BookIT Member", { exact: true })).toBeVisible(); + await openBooking(page, "E2E session expiry booking"); +}); + +test("an expired BookIT session can sign in again through an existing Gamma session", async ({ + page, + environment, +}) => { + await createBooking(page, "E2E Gamma session booking"); + await page.context().clearCookies({ domain: "localhost" }); + await page.getByRole("button", { name: /next/i }).click(); + + await expect(page).toHaveURL(`${environment.appUrl}/`); + await openBooking(page, "E2E Gamma session booking"); +}); + test("an outsider can view bookings but cannot access editing or administration", async ({ page, environment, diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 5ad005a..8cf9253 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -1,6 +1,8 @@ import { print } from "graphql"; import type { TypedDocumentNode } from "@graphql-typed-document-node/core"; +let redirectingToLogin = false; + export async function request<T, V extends Record<string, unknown>>( document: TypedDocumentNode<T, V>, variables: V, @@ -13,6 +15,11 @@ export async function request<T, V extends Record<string, unknown>>( }); if (response.status === 401 || response.redirected) { + if (!redirectingToLogin) { + redirectingToLogin = true; + window.location.replace("/api/login"); + } + throw new Error("Your session expired. Please sign in again."); } From b5ee44cc34e121f5e746b1dc4e758c0f25429e3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 15:08:34 +0200 Subject: [PATCH 32/41] Serve the frontend from a single BookIT application image --- .dockerignore | 1 + .github/workflows/ci.yml | 30 ++++++---------- .github/workflows/release.yml | 18 +++++----- .gitignore | 1 + backend/Dockerfile => Dockerfile | 21 +++++------ README.md | 15 ++++---- {backend => bookit}/.env.example | 0 {backend => bookit}/package.json | 3 +- {backend => bookit}/prisma.config.ts | 0 {backend => bookit}/prisma/schema.prisma | 0 {backend => bookit}/src/auth/session-store.ts | 0 {backend => bookit}/src/auth/user.ts | 0 {backend => bookit}/src/generated/schema.ts | 0 {backend => bookit}/src/index.ts | 0 {backend => bookit}/src/models/error.ts | 0 {backend => bookit}/src/models/index.ts | 0 {backend => bookit}/src/models/user.ts | 0 .../src/resolvers/event.resolver.ts | 0 .../src/resolvers/illegal_slots.resolver.ts | 0 {backend => bookit}/src/resolvers/index.ts | 0 .../src/resolvers/rule.resolver.ts | 0 .../src/resolvers/serialize.ts | 0 .../src/resolvers/user.resolver.ts | 0 {backend => bookit}/src/routes/index.ts | 19 +++++++++- {backend => bookit}/src/schemas/v1/error.gql | 0 {backend => bookit}/src/schemas/v1/event.gql | 0 .../src/schemas/v1/illegal_slots.gql | 0 {backend => bookit}/src/schemas/v1/room.gql | 0 {backend => bookit}/src/schemas/v1/rule.gql | 0 {backend => bookit}/src/schemas/v1/user.gql | 0 .../src/services/event.service.ts | 0 .../src/services/rule.service.ts | 0 {backend => bookit}/src/tests/rule.test.ts | 0 {backend => bookit}/src/utils/commonTypes.ts | 0 {backend => bookit}/src/utils/index.ts | 0 {backend => bookit}/startup.sh | 0 {backend => bookit}/tsconfig.build.json | 0 {backend => bookit}/tsconfig.json | 0 codegen.ts | 6 ++-- e2e/Makefile | 34 +++++++----------- e2e/README.md | 17 +++++---- e2e/booking-lifecycle.spec.ts | 1 + e2e/compose.ts | 35 +++++-------------- frontend/Dockerfile | 15 -------- frontend/default.conf | 11 ------ frontend/vite.config.ts | 3 +- package.json | 4 +-- pnpm-lock.yaml | 2 +- pnpm-workspace.yaml | 2 +- tsconfig.json | 2 +- vite.config.ts | 4 ++- 51 files changed, 102 insertions(+), 142 deletions(-) rename backend/Dockerfile => Dockerfile (57%) rename {backend => bookit}/.env.example (100%) rename {backend => bookit}/package.json (97%) rename {backend => bookit}/prisma.config.ts (100%) rename {backend => bookit}/prisma/schema.prisma (100%) rename {backend => bookit}/src/auth/session-store.ts (100%) rename {backend => bookit}/src/auth/user.ts (100%) rename {backend => bookit}/src/generated/schema.ts (100%) rename {backend => bookit}/src/index.ts (100%) rename {backend => bookit}/src/models/error.ts (100%) rename {backend => bookit}/src/models/index.ts (100%) rename {backend => bookit}/src/models/user.ts (100%) rename {backend => bookit}/src/resolvers/event.resolver.ts (100%) rename {backend => bookit}/src/resolvers/illegal_slots.resolver.ts (100%) rename {backend => bookit}/src/resolvers/index.ts (100%) rename {backend => bookit}/src/resolvers/rule.resolver.ts (100%) rename {backend => bookit}/src/resolvers/serialize.ts (100%) rename {backend => bookit}/src/resolvers/user.resolver.ts (100%) rename {backend => bookit}/src/routes/index.ts (67%) rename {backend => bookit}/src/schemas/v1/error.gql (100%) rename {backend => bookit}/src/schemas/v1/event.gql (100%) rename {backend => bookit}/src/schemas/v1/illegal_slots.gql (100%) rename {backend => bookit}/src/schemas/v1/room.gql (100%) rename {backend => bookit}/src/schemas/v1/rule.gql (100%) rename {backend => bookit}/src/schemas/v1/user.gql (100%) rename {backend => bookit}/src/services/event.service.ts (100%) rename {backend => bookit}/src/services/rule.service.ts (100%) rename {backend => bookit}/src/tests/rule.test.ts (100%) rename {backend => bookit}/src/utils/commonTypes.ts (100%) rename {backend => bookit}/src/utils/index.ts (100%) rename {backend => bookit}/startup.sh (100%) rename {backend => bookit}/tsconfig.build.json (100%) rename {backend => bookit}/tsconfig.json (100%) delete mode 100644 frontend/Dockerfile delete mode 100644 frontend/default.conf diff --git a/.dockerignore b/.dockerignore index b618b4f..67ea70e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,6 +5,7 @@ !**/.env.example **/build **/dist +bookit/public **/.vite **/.cache **/coverage diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8b13885..0d66af3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,16 +42,16 @@ jobs: - name: Generate GraphQL contracts and check they are committed run: | pnpm codegen - git diff --exit-code -- backend/src/generated/schema.ts frontend/src/generated/graphql.ts + git diff --exit-code -- bookit/src/generated/schema.ts frontend/src/generated/graphql.ts - name: Generate Prisma client - run: pnpm --dir backend exec prisma generate + run: pnpm --dir bookit exec prisma generate - name: Check strict TypeScript contracts run: pnpm typecheck - name: Check formatting and type-aware lint run: vp check - name: Run unit and API contract tests run: pnpm test - - name: Build backend and frontend + - name: Build BookIT run: pnpm build - name: Audit production and development dependencies run: pnpm audit --audit-level low @@ -60,7 +60,7 @@ jobs: run: echo "Image publishing and image E2E are skipped for fork/Dependabot PRs. After reviewing the changes, a maintainer must put them on a trusted branch in this repository to run the image pipeline. No registry credentials are supplied to this job." >> "$GITHUB_STEP_SUMMARY" images: - name: Publish commit images + name: Publish BookIT commit image needs: quality if: github.event_name != 'pull_request' && github.actor != 'dependabot[bot]' runs-on: ubuntu-24.04 @@ -79,29 +79,19 @@ jobs: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Publish frontend commit + - name: Publish BookIT commit uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: context: . - file: frontend/Dockerfile + file: Dockerfile platforms: linux/amd64 push: true - tags: ghcr.io/cthit/bookit-node-frontend:${{ github.sha }} - cache-from: type=gha,scope=frontend - cache-to: type=gha,mode=max,scope=frontend - - name: Publish backend commit - uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 - with: - context: . - file: backend/Dockerfile - platforms: linux/amd64 - push: true - tags: ghcr.io/cthit/bookit-node-backend:${{ github.sha }} - cache-from: type=gha,scope=backend - cache-to: type=gha,mode=max,scope=backend + tags: ghcr.io/cthit/bookit:${{ github.sha }} + cache-from: type=gha,scope=bookit + cache-to: type=gha,mode=max,scope=bookit e2e: - name: Browser flows against published images + name: Browser flows against published BookIT needs: images runs-on: ubuntu-24.04 timeout-minutes: 30 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 65a3649..79ed1e3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -name: Release images +name: Release BookIT on: release: @@ -15,7 +15,7 @@ concurrency: jobs: release: - name: Tag tested images + name: Tag tested BookIT image runs-on: ubuntu-24.04 timeout-minutes: 10 env: @@ -47,11 +47,9 @@ jobs: - name: Apply the release tag without rebuilding shell: bash run: | - for component in frontend backend; do - repository="ghcr.io/cthit/bookit-node-$component" - digest="$(docker buildx imagetools inspect "$repository:$GITHUB_SHA" --format '{{.Manifest.Digest}}')" - target="$repository:$RELEASE_TAG" - docker buildx imagetools create --prefer-index=false --tag "$target" "$repository@$digest" - actual="$(docker buildx imagetools inspect "$target" --format '{{.Manifest.Digest}}')" - test "$actual" = "$digest" - done + repository="ghcr.io/cthit/bookit" + digest="$(docker buildx imagetools inspect "$repository:$GITHUB_SHA" --format '{{.Manifest.Digest}}')" + target="$repository:$RELEASE_TAG" + docker buildx imagetools create --prefer-index=false --tag "$target" "$repository@$digest" + actual="$(docker buildx imagetools inspect "$target" --format '{{.Manifest.Digest}}')" + test "$actual" = "$digest" diff --git a/.gitignore b/.gitignore index 93d2525..6920d88 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ package-lock.json !.env.example .pnpm-store/ dist/ +bookit/public/ .vite/ playwright-report/ test-results/ diff --git a/backend/Dockerfile b/Dockerfile similarity index 57% rename from backend/Dockerfile rename to Dockerfile index 816d784..646babb 100644 --- a/backend/Dockerfile +++ b/Dockerfile @@ -7,19 +7,20 @@ WORKDIR /workspace FROM base AS build ENV CI=true COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ -COPY backend/package.json ./backend/package.json +COPY bookit/package.json ./bookit/package.json COPY frontend/package.json ./frontend/package.json -COPY backend/prisma ./backend/prisma -COPY backend/prisma.config.ts ./backend/prisma.config.ts -RUN pnpm install --frozen-lockfile --filter backend -COPY backend ./backend -RUN pnpm --dir backend exec prisma generate \ - && pnpm --dir backend build \ - && node -e 'require("node:fs").cpSync("backend/src/schemas", "backend/build/schemas", { recursive: true })' \ - && pnpm --filter backend deploy --prod --legacy /app \ +COPY bookit/prisma ./bookit/prisma +COPY bookit/prisma.config.ts ./bookit/prisma.config.ts +RUN pnpm install --frozen-lockfile --filter bookit --filter @bookit/frontend +COPY bookit ./bookit +COPY frontend ./frontend +RUN pnpm --dir bookit exec prisma generate \ + && pnpm --dir bookit build \ + && pnpm --dir frontend build \ + && node -e 'require("node:fs").cpSync("bookit/src/schemas", "bookit/build/schemas", { recursive: true })' \ + && pnpm --filter bookit deploy --prod --legacy /app \ && node -e 'require("node:fs").copyFileSync("pnpm-workspace.yaml", "/app/pnpm-workspace.yaml")' -# Deployment creates a fresh node_modules layout; generate the client there. WORKDIR /app RUN ./node_modules/.bin/prisma generate diff --git a/README.md b/README.md index 6bd8218..73ebd15 100644 --- a/README.md +++ b/README.md @@ -23,22 +23,22 @@ corepack enable corepack prepare pnpm@12.3.4 --activate pnpm install --frozen-lockfile pnpm codegen -pnpm --dir backend exec prisma generate +pnpm --dir bookit exec prisma generate docker compose up -d --wait db redis gamma docker compose run --rm gamma-init -test -f backend/.env || cp backend/.env.example backend/.env +test -f bookit/.env || cp bookit/.env.example bookit/.env ``` Compose starts local Gamma, its test users and BookIT OAuth client, and separate PostgreSQL/Redis instances for each app. Copy the local authentication values from -`backend/.env.example` if you already have an `.env`, and set `SESSION_SECRET` +`bookit/.env.example` if you already have an `.env`, and set `SESSION_SECRET` using `openssl rand -hex 32`. No production credentials are needed. -Backend, after confirming `DATABASE_URL` points to your local database: +BookIT, after confirming `DATABASE_URL` points to your local database: ```sh -pnpm --dir backend migrate -pnpm --dir backend dev +pnpm --dir bookit migrate +pnpm --dir bookit dev ``` Frontend, in another terminal: @@ -56,7 +56,8 @@ Ordinary startup preserves database contents; do not use `docker compose down -v to stop development. Use `docker compose stop` instead. Compose is development-only; deployments must provide their own authentication settings with `NODE_ENV=production`. Existing deployments can keep `SECRET` for OIDC; `SESSION_SECRET` is used if `SECRET` is absent. -Deploy matching frontend/backend versions together; open tabs may need a refresh and a new sign-in. +The `ghcr.io/cthit/bookit` image serves both the API and built frontend on port 8080. +Vite is only used as a separate server during development for live updates. Run `pnpm check`, `pnpm test` and `pnpm build` for the project checks. See [browser tests](e2e/README.md) for Playwright setup and CI image testing. diff --git a/backend/.env.example b/bookit/.env.example similarity index 100% rename from backend/.env.example rename to bookit/.env.example diff --git a/backend/package.json b/bookit/package.json similarity index 97% rename from backend/package.json rename to bookit/package.json index f5a321d..bb31bff 100644 --- a/backend/package.json +++ b/bookit/package.json @@ -1,11 +1,12 @@ { - "name": "backend", + "name": "bookit", "version": "1.0.0", "description": "", "license": "ISC", "author": "", "files": [ "build", + "public", "prisma", "prisma.config.ts", "startup.sh" diff --git a/backend/prisma.config.ts b/bookit/prisma.config.ts similarity index 100% rename from backend/prisma.config.ts rename to bookit/prisma.config.ts diff --git a/backend/prisma/schema.prisma b/bookit/prisma/schema.prisma similarity index 100% rename from backend/prisma/schema.prisma rename to bookit/prisma/schema.prisma diff --git a/backend/src/auth/session-store.ts b/bookit/src/auth/session-store.ts similarity index 100% rename from backend/src/auth/session-store.ts rename to bookit/src/auth/session-store.ts diff --git a/backend/src/auth/user.ts b/bookit/src/auth/user.ts similarity index 100% rename from backend/src/auth/user.ts rename to bookit/src/auth/user.ts diff --git a/backend/src/generated/schema.ts b/bookit/src/generated/schema.ts similarity index 100% rename from backend/src/generated/schema.ts rename to bookit/src/generated/schema.ts diff --git a/backend/src/index.ts b/bookit/src/index.ts similarity index 100% rename from backend/src/index.ts rename to bookit/src/index.ts diff --git a/backend/src/models/error.ts b/bookit/src/models/error.ts similarity index 100% rename from backend/src/models/error.ts rename to bookit/src/models/error.ts diff --git a/backend/src/models/index.ts b/bookit/src/models/index.ts similarity index 100% rename from backend/src/models/index.ts rename to bookit/src/models/index.ts diff --git a/backend/src/models/user.ts b/bookit/src/models/user.ts similarity index 100% rename from backend/src/models/user.ts rename to bookit/src/models/user.ts diff --git a/backend/src/resolvers/event.resolver.ts b/bookit/src/resolvers/event.resolver.ts similarity index 100% rename from backend/src/resolvers/event.resolver.ts rename to bookit/src/resolvers/event.resolver.ts diff --git a/backend/src/resolvers/illegal_slots.resolver.ts b/bookit/src/resolvers/illegal_slots.resolver.ts similarity index 100% rename from backend/src/resolvers/illegal_slots.resolver.ts rename to bookit/src/resolvers/illegal_slots.resolver.ts diff --git a/backend/src/resolvers/index.ts b/bookit/src/resolvers/index.ts similarity index 100% rename from backend/src/resolvers/index.ts rename to bookit/src/resolvers/index.ts diff --git a/backend/src/resolvers/rule.resolver.ts b/bookit/src/resolvers/rule.resolver.ts similarity index 100% rename from backend/src/resolvers/rule.resolver.ts rename to bookit/src/resolvers/rule.resolver.ts diff --git a/backend/src/resolvers/serialize.ts b/bookit/src/resolvers/serialize.ts similarity index 100% rename from backend/src/resolvers/serialize.ts rename to bookit/src/resolvers/serialize.ts diff --git a/backend/src/resolvers/user.resolver.ts b/bookit/src/resolvers/user.resolver.ts similarity index 100% rename from backend/src/resolvers/user.resolver.ts rename to bookit/src/resolvers/user.resolver.ts diff --git a/backend/src/routes/index.ts b/bookit/src/routes/index.ts similarity index 67% rename from backend/src/routes/index.ts rename to bookit/src/routes/index.ts index 21dfb0e..a905801 100644 --- a/backend/src/routes/index.ts +++ b/bookit/src/routes/index.ts @@ -31,7 +31,24 @@ export const setupRoutes = async (app: express.Application, tools: Tools, httpSe }), ); - app.use("/", proxy(process.env.FRONTEND_URL || "http://localhost:3001")); + app.use("/api", (_req, res) => res.status(404).json({ error: "Not found" })); + + if (process.env.NODE_ENV === "development") { + app.use("/", proxy(process.env.FRONTEND_URL || "http://localhost:3001")); + } else { + const publicDirectory = join(__dirname, "../../public"); + + app.use( + "/assets", + express.static(join(publicDirectory, "assets"), { maxAge: "1y", immutable: true }), + (_req, res) => res.sendStatus(404), + ); + app.use(express.static(publicDirectory, { index: false })); + app.get("/{*path}", (_req, res) => { + res.set("Cache-Control", "no-store"); + res.sendFile(join(publicDirectory, "index.html")); + }); + } return server; }; diff --git a/backend/src/schemas/v1/error.gql b/bookit/src/schemas/v1/error.gql similarity index 100% rename from backend/src/schemas/v1/error.gql rename to bookit/src/schemas/v1/error.gql diff --git a/backend/src/schemas/v1/event.gql b/bookit/src/schemas/v1/event.gql similarity index 100% rename from backend/src/schemas/v1/event.gql rename to bookit/src/schemas/v1/event.gql diff --git a/backend/src/schemas/v1/illegal_slots.gql b/bookit/src/schemas/v1/illegal_slots.gql similarity index 100% rename from backend/src/schemas/v1/illegal_slots.gql rename to bookit/src/schemas/v1/illegal_slots.gql diff --git a/backend/src/schemas/v1/room.gql b/bookit/src/schemas/v1/room.gql similarity index 100% rename from backend/src/schemas/v1/room.gql rename to bookit/src/schemas/v1/room.gql diff --git a/backend/src/schemas/v1/rule.gql b/bookit/src/schemas/v1/rule.gql similarity index 100% rename from backend/src/schemas/v1/rule.gql rename to bookit/src/schemas/v1/rule.gql diff --git a/backend/src/schemas/v1/user.gql b/bookit/src/schemas/v1/user.gql similarity index 100% rename from backend/src/schemas/v1/user.gql rename to bookit/src/schemas/v1/user.gql diff --git a/backend/src/services/event.service.ts b/bookit/src/services/event.service.ts similarity index 100% rename from backend/src/services/event.service.ts rename to bookit/src/services/event.service.ts diff --git a/backend/src/services/rule.service.ts b/bookit/src/services/rule.service.ts similarity index 100% rename from backend/src/services/rule.service.ts rename to bookit/src/services/rule.service.ts diff --git a/backend/src/tests/rule.test.ts b/bookit/src/tests/rule.test.ts similarity index 100% rename from backend/src/tests/rule.test.ts rename to bookit/src/tests/rule.test.ts diff --git a/backend/src/utils/commonTypes.ts b/bookit/src/utils/commonTypes.ts similarity index 100% rename from backend/src/utils/commonTypes.ts rename to bookit/src/utils/commonTypes.ts diff --git a/backend/src/utils/index.ts b/bookit/src/utils/index.ts similarity index 100% rename from backend/src/utils/index.ts rename to bookit/src/utils/index.ts diff --git a/backend/startup.sh b/bookit/startup.sh similarity index 100% rename from backend/startup.sh rename to bookit/startup.sh diff --git a/backend/tsconfig.build.json b/bookit/tsconfig.build.json similarity index 100% rename from backend/tsconfig.build.json rename to bookit/tsconfig.build.json diff --git a/backend/tsconfig.json b/bookit/tsconfig.json similarity index 100% rename from backend/tsconfig.json rename to bookit/tsconfig.json diff --git a/codegen.ts b/codegen.ts index fe91e2f..9b70799 100644 --- a/codegen.ts +++ b/codegen.ts @@ -10,7 +10,7 @@ import { mergeTypeDefs } from "@graphql-tools/merge"; import { type DocumentNode } from "graphql"; async function generate() { - const schema = mergeTypeDefs(loadFilesSync("backend/src/schemas/v1/*.gql")); + const schema = mergeTypeDefs(loadFilesSync("bookit/src/schemas/v1/*.gql")); const documents = loadFilesSync<DocumentNode>("frontend/src/**/*.graphql").map((document) => ({ document, @@ -31,11 +31,11 @@ async function generate() { }), }, { - filename: "backend/src/generated/schema.ts", + filename: "bookit/src/generated/schema.ts", content: await codegen({ schema, documents: [], - filename: "backend/src/generated/schema.ts", + filename: "bookit/src/generated/schema.ts", config: { ...config, contextType: "../utils/commonTypes#Context" }, plugins: [{ typescript: {} }, { resolvers: {} }], pluginMap: { typescript, resolvers }, diff --git a/e2e/Makefile b/e2e/Makefile index d4becf4..9720bcb 100644 --- a/e2e/Makefile +++ b/e2e/Makefile @@ -1,36 +1,28 @@ export VERSION ?= -export BOOKIT_FRONTEND_IMAGE ?= -export BOOKIT_BACKEND_IMAGE ?= +export BOOKIT_IMAGE ?= .PHONY: run-e2e run-e2e: @set -eu; \ - local_frontend=""; local_backend=""; \ + local_image=""; \ cleanup() { \ - if [ -n "$$local_frontend" ]; then docker image rm "$$local_frontend" || true; fi; \ - if [ -n "$$local_backend" ]; then docker image rm "$$local_backend" || true; fi; \ + if [ -n "$$local_image" ]; then docker image rm "$$local_image" || true; fi; \ }; \ trap cleanup EXIT; trap 'exit 130' INT; trap 'exit 143' TERM; \ if [ -n "$$VERSION" ]; then \ - if [ -n "$$BOOKIT_FRONTEND_IMAGE$$BOOKIT_BACKEND_IMAGE" ]; then \ - echo "Use VERSION or explicit image references, not both" >&2; exit 1; \ + if [ -n "$$BOOKIT_IMAGE" ]; then \ + echo "Use VERSION or BOOKIT_IMAGE, not both" >&2; exit 1; \ fi; \ - BOOKIT_FRONTEND_IMAGE="ghcr.io/cthit/bookit-node-frontend:$$VERSION"; \ - BOOKIT_BACKEND_IMAGE="ghcr.io/cthit/bookit-node-backend:$$VERSION"; \ + BOOKIT_IMAGE="ghcr.io/cthit/bookit:$$VERSION"; \ fi; \ - if [ -n "$$BOOKIT_FRONTEND_IMAGE$$BOOKIT_BACKEND_IMAGE" ]; then \ - : "$${BOOKIT_FRONTEND_IMAGE:?Both image references are required}"; \ - : "$${BOOKIT_BACKEND_IMAGE:?Both image references are required}"; \ - docker pull --platform linux/amd64 "$$BOOKIT_FRONTEND_IMAGE"; \ - docker pull --platform linux/amd64 "$$BOOKIT_BACKEND_IMAGE"; \ + if [ -n "$$BOOKIT_IMAGE" ]; then \ + docker pull --platform linux/amd64 "$$BOOKIT_IMAGE"; \ elif [ -n "$${CI:-}" ]; then \ - echo "CI requires VERSION or explicit image references" >&2; exit 1; \ + echo "CI requires VERSION or BOOKIT_IMAGE" >&2; exit 1; \ else \ - local_frontend="bookit-e2e-frontend:run-$$$$"; \ - local_backend="bookit-e2e-backend:run-$$$$"; \ - docker build --pull --platform linux/amd64 -f ../frontend/Dockerfile -t "$$local_frontend" ..; \ - docker build --pull --platform linux/amd64 -f ../backend/Dockerfile -t "$$local_backend" ..; \ - BOOKIT_FRONTEND_IMAGE="$$local_frontend"; BOOKIT_BACKEND_IMAGE="$$local_backend"; \ + local_image="bookit-e2e:run-$$$$"; \ + docker build --pull --platform linux/amd64 -f ../Dockerfile -t "$$local_image" ..; \ + BOOKIT_IMAGE="$$local_image"; \ fi; \ - export BOOKIT_FRONTEND_IMAGE BOOKIT_BACKEND_IMAGE; \ + export BOOKIT_IMAGE; \ pnpm --dir .. exec playwright test diff --git a/e2e/README.md b/e2e/README.md index df55cc3..6b84239 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -7,25 +7,24 @@ From the repository root: ```sh make -C e2e run-e2e # Build and test the current checkout -make -C e2e run-e2e VERSION='<sha>' # Test published commit images +make -C e2e run-e2e VERSION='<sha>' # Test a published commit image make -C e2e run-e2e VERSION=v1.2.0 # Test a release or any other published tag ``` `pnpm test:e2e` runs the same Make target. For private GHCR images, first use `docker login ghcr.io` with read-only package access. -To test specific digests instead of a shared tag: +To test a specific image digest: ```sh make -C e2e run-e2e \ - BOOKIT_FRONTEND_IMAGE='ghcr.io/cthit/bookit-node-frontend@sha256:<digest>' \ - BOOKIT_BACKEND_IMAGE='ghcr.io/cthit/bookit-node-backend@sha256:<digest>' + BOOKIT_IMAGE='ghcr.io/cthit/bookit@sha256:<digest>' ``` Every run uses fresh Testcontainers-managed containers, volumes and a network: -BookIT frontend/backend, Gamma, and separate PostgreSQL/Redis instances for each -app. The backend image initializes the empty BookIT database before startup. -Requests go through the backend to the Nginx frontend, as in production. +BookIT, Gamma, and separate PostgreSQL/Redis instances for each app. +The BookIT image initializes the empty database before startup, then serves both +the API and built frontend, as in production. No development servers or existing databases are used. Tests log in through Gamma with member/admin/outsider fixtures and reset bookings @@ -37,10 +36,10 @@ image tags are removed on exit. Build caches and unrelated Docker resources are left alone. Published images are pulled on every run. Failures retain Playwright traces, screenshots, video and service logs. -CI publishes commit images after quality checks and runs this same target with +CI publishes a commit image after quality checks and runs this same target with `VERSION`. Fork/Dependabot PRs get quality checks only, without registry credentials; image E2E requires a reviewed branch in this repository. GitHub releases retag -the tested commit images without rebuilding. +the tested commit image without rebuilding. Service versions are pinned in `compose.ts`. BookIT's PostgreSQL 12 and Redis 5 upgrades remain separate follow-ups. diff --git a/e2e/booking-lifecycle.spec.ts b/e2e/booking-lifecycle.spec.ts index b7741a0..fd39709 100644 --- a/e2e/booking-lifecycle.spec.ts +++ b/e2e/booking-lifecycle.spec.ts @@ -16,6 +16,7 @@ test("a group member creates, edits and deletes a persisted booking", async ({ p await expect(page.getByRole("heading", { name: title, exact: true })).toBeVisible(); await page.getByRole("button", { name: "Edit", exact: true }).click(); + await page.reload(); await expect(page.getByRole("textbox", { name: "Phone number", exact: true })).toHaveValue( "0701234567", diff --git a/e2e/compose.ts b/e2e/compose.ts index 42c2473..e559be1 100644 --- a/e2e/compose.ts +++ b/e2e/compose.ts @@ -169,10 +169,9 @@ export async function loginAs( } export async function compose(browser: Browser): Promise<Environment> { - const frontendImage = process.env.BOOKIT_FRONTEND_IMAGE; - const backendImage = process.env.BOOKIT_BACKEND_IMAGE; + const image = process.env.BOOKIT_IMAGE; - if (!frontendImage || !backendImage) { + if (!image) { throw new Error("Run E2E with make -C e2e run-e2e"); } @@ -320,25 +319,21 @@ export async function compose(browser: Browser): Promise<Environment> { SESSION_SECRET: "secret", SECRET: "bookit-isolated-e2e-oidc-secret-at-least-32-characters", BASE_URL: appUrl, - BACKEND_URL: appUrl, PORT: "8080", - FRONTEND_URL: "http://bookit-frontend:80", ISSUER_BASE_URL: gammaUrl, CLIENT_ID: client.clientId, CLIENT_SECRET: client.clientSecret, API_KEY: client.apiKey, }; - record("images", JSON.stringify({ frontend: frontendImage, backend: backendImage })); + record("image", image); - console.log( - `Using BookIT images: ${JSON.stringify({ frontend: frontendImage, backend: backendImage })}`, - ); + console.log(`Using BookIT image: ${image}`); - console.log("Initializing the isolated database with the backend image..."); + console.log("Initializing the isolated database with the BookIT image..."); await track( - new GenericContainer(backendImage) + new GenericContainer(image) .withPlatform("linux/amd64") .withNetwork(network) .withEnvironment({ DATABASE_URL: env.DATABASE_URL }) @@ -350,26 +345,12 @@ export async function compose(browser: Browser): Promise<Environment> { ); await track( - new GenericContainer(frontendImage) - .withPlatform("linux/amd64") - .withNetwork(network) - .withNetworkAliases("bookit-frontend") - .withExposedPorts(80) - .withLogConsumer((stream) => - stream.on("data", (chunk: Buffer) => record("frontend", chunk)), - ) - .withWaitStrategy(Wait.forHttp("/", 80).forStatusCode(200)) - .withStartupTimeout(120_000) - .start(), - ); - - await track( - new GenericContainer(backendImage) + new GenericContainer(image) .withPlatform("linux/amd64") .withNetwork(network) .withEnvironment(env) .withExposedPorts({ container: 8080, host: appPort }) - .withLogConsumer((stream) => stream.on("data", (chunk: Buffer) => record("backend", chunk))) + .withLogConsumer((stream) => stream.on("data", (chunk: Buffer) => record("bookit", chunk))) .withWaitStrategy(Wait.forHttp("/api/health", 8080).forStatusCode(200)) .withStartupTimeout(120_000) .start(), diff --git a/frontend/Dockerfile b/frontend/Dockerfile deleted file mode 100644 index 7b61725..0000000 --- a/frontend/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM node:24.19.0-bookworm-slim AS build -ENV CI=true COREPACK_HOME=/opt/corepack -RUN corepack enable && corepack prepare pnpm@12.3.4 --activate -WORKDIR /workspace -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ -COPY frontend/package.json ./frontend/package.json -COPY backend/package.json ./backend/package.json -RUN pnpm install --frozen-lockfile --filter @bookit/frontend -COPY frontend ./frontend -RUN pnpm --dir frontend build - -FROM nginx:1.30.0-alpine -COPY frontend/default.conf /etc/nginx/conf.d/default.conf -COPY --from=build /workspace/frontend/dist /usr/share/nginx/html -EXPOSE 80 diff --git a/frontend/default.conf b/frontend/default.conf deleted file mode 100644 index 8c672b0..0000000 --- a/frontend/default.conf +++ /dev/null @@ -1,11 +0,0 @@ -server { - listen 80; - server_tokens off; - add_header X-Content-Type-Options nosniff always; - add_header Referrer-Policy same-origin always; - location / { - root /usr/share/nginx/html; - index index.html index.htm; - try_files $uri $uri/ /index.html; - } -} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 7022474..4c12b3f 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -6,10 +6,11 @@ import tailwindcss from "@tailwindcss/vite"; export default defineConfig({ plugins: [react(), tailwindcss()], resolve: { alias: { "@": fileURLToPath(new URL("./src", import.meta.url)) } }, + build: { outDir: "../bookit/public", emptyOutDir: true }, server: { host: "127.0.0.1", port: 3001, strictPort: true, - proxy: { "/api": process.env.BACKEND_URL ?? "http://127.0.0.1:8080" }, + proxy: { "/api": process.env.BOOKIT_URL ?? "http://127.0.0.1:8080" }, }, }); diff --git a/package.json b/package.json index d5830f4..acd2d13 100644 --- a/package.json +++ b/package.json @@ -3,13 +3,13 @@ "private": true, "scripts": { "codegen": "tsx codegen.ts", - "build": "pnpm codegen && pnpm --dir backend exec prisma generate && pnpm --dir backend build && pnpm --dir frontend build", + "build": "pnpm codegen && pnpm --dir bookit exec prisma generate && pnpm --dir bookit build && pnpm --dir frontend build", "test": "vp test run", "check": "vp check", "lint": "vp lint --type-aware", "format": "vp fmt", "format:check": "vp fmt --check", - "typecheck": "pnpm --dir backend typecheck && pnpm --dir frontend typecheck && tsc --noEmit -p e2e/tsconfig.json", + "typecheck": "pnpm --dir bookit typecheck && pnpm --dir frontend typecheck && tsc --noEmit -p e2e/tsconfig.json", "test:e2e": "make -C e2e run-e2e" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 38c488b..ef87554 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -162,7 +162,7 @@ importers: specifier: 0.3.0 version: 0.3.0(@types/node@24.13.3)(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(typescript@7.0.2)(yaml@2.9.0) - backend: + bookit: dependencies: '@apollo/server': specifier: ^5.5.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 8b96b72..7089f91 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,5 +1,5 @@ packages: - - backend + - bookit - frontend overrides: diff --git a/tsconfig.json b/tsconfig.json index fcf969f..9cb22d6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,4 +1,4 @@ { "files": [], - "references": [{ "path": "./frontend" }, { "path": "./backend" }, { "path": "./e2e" }] + "references": [{ "path": "./frontend" }, { "path": "./bookit" }, { "path": "./e2e" }] } diff --git a/vite.config.ts b/vite.config.ts index 53d77f1..ceb3f7a 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from "vite-plus"; export default defineConfig({ test: { - include: ["backend/src/**/*.test.ts", "frontend/src/**/*.test.ts"], + include: ["bookit/src/**/*.test.ts", "frontend/src/**/*.test.ts"], environment: "node", }, lint: { @@ -27,6 +27,7 @@ export default defineConfig({ "**/generated/**", "**/build/**", "**/dist/**", + "bookit/public/**", "**/node_modules/**", "playwright-report/**", "test-results/**", @@ -38,6 +39,7 @@ export default defineConfig({ "pnpm-lock.yaml", "**/build/**", "**/dist/**", + "bookit/public/**", "playwright-report/**", "test-results/**", ], From dbe631eed5c52e312dd75b9bd6accfef5631ca31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 16:57:58 +0200 Subject: [PATCH 33/41] Fix booking review findings and preserve main API compatibility --- README.md | 44 ++++ bookit/src/auth/user.ts | 3 + bookit/src/generated/schema.ts | 61 +++-- bookit/src/models/user.ts | 3 + bookit/src/resolvers/event.resolver.ts | 52 +++- .../src/resolvers/illegal_slots.resolver.ts | 4 +- bookit/src/resolvers/index.ts | 3 +- bookit/src/resolvers/input.ts | 16 ++ bookit/src/resolvers/rule.resolver.ts | 20 +- bookit/src/schemas/v1/event.gql | 19 +- bookit/src/schemas/v1/rule.gql | 8 +- bookit/src/schemas/v1/user.gql | 5 + bookit/src/services/event.service.ts | 88 ++++++- bookit/src/services/rule.service.ts | 89 +++---- bookit/src/tests/event.test.ts | 193 +++++++++++++++ bookit/src/tests/fixtures/main-schema.graphql | 124 ++++++++++ bookit/src/tests/rule.test.ts | 65 ++++- bookit/src/tests/schema-compatibility.test.ts | 100 ++++++++ bookit/src/tests/session-store.test.ts | 65 +++++ bookit/src/utils/date-range.ts | 27 +++ compose.cleanup.yml | 7 + db-scripts/cleanup.sh | 15 +- docker-compose.yml | 13 + e2e/README.md | 15 ++ e2e/booking-lifecycle.spec.ts | 45 ++++ e2e/calendar.spec.ts | 39 +++ e2e/cleanup.spec.ts | 52 ++++ e2e/compose.ts | 89 ++++--- e2e/permissions.spec.ts | 28 +++ frontend/src/api/operations.graphql | 24 +- frontend/src/components/booking-form.tsx | 10 + .../src/components/event-calendar-toolbar.tsx | 12 - frontend/src/components/event-calendar.tsx | 36 +-- frontend/src/components/rule-details.tsx | 44 ++++ frontend/src/components/rule-form.tsx | 158 ++++++++++++ .../components/ui/event-calendar-views.tsx | 22 -- frontend/src/generated/graphql.ts | 26 +- frontend/src/lib/dates.test.ts | 19 +- frontend/src/lib/dates.ts | 12 + frontend/src/lib/rules.ts | 17 ++ frontend/src/pages/calendar.tsx | 25 +- frontend/src/pages/rules.tsx | 228 ++---------------- frontend/src/router.tsx | 11 +- 43 files changed, 1491 insertions(+), 445 deletions(-) create mode 100644 bookit/src/resolvers/input.ts create mode 100644 bookit/src/tests/event.test.ts create mode 100644 bookit/src/tests/fixtures/main-schema.graphql create mode 100644 bookit/src/tests/schema-compatibility.test.ts create mode 100644 bookit/src/tests/session-store.test.ts create mode 100644 bookit/src/utils/date-range.ts create mode 100644 compose.cleanup.yml create mode 100644 e2e/cleanup.spec.ts create mode 100644 frontend/src/components/rule-details.tsx create mode 100644 frontend/src/components/rule-form.tsx create mode 100644 frontend/src/lib/rules.ts diff --git a/README.md b/README.md index 73ebd15..b0e68f5 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ BookIT, after confirming `DATABASE_URL` points to your local database: ```sh pnpm --dir bookit migrate +docker compose up -d db-scripts pnpm --dir bookit dev ``` @@ -59,6 +60,49 @@ Existing deployments can keep `SECRET` for OIDC; `SESSION_SECRET` is used if `SE The `ghcr.io/cthit/bookit` image serves both the API and built frontend on port 8080. Vite is only used as a separate server during development for live updates. +## Personal-data cleanup + +Run the cleanup service alongside every deployment. It clears phone numbers and +CIDs from bookings that ended at least two weeks ago, runs immediately and then +daily, and retries failures after an hour. The application image does not schedule +this job itself. Development Compose includes `db-scripts`. + +For production, export the application's `DATABASE_URL` and run: + +```sh +docker compose -f compose.cleanup.yml up -d --build +docker compose -f compose.cleanup.yml logs db-scripts +``` + +For an immediate one-time verification, run +`docker compose -f compose.cleanup.yml run --rm -e CLEANUP_ONCE=1 db-scripts`. +It exits with a failure status if the cleanup query fails. + +The database must be reachable from that Compose network. If it runs in Docker, +attach the cleanup service to the database's existing network through your +deployment's Compose configuration. Existing cleanup deployments may continue to +use `DB_HOST`, `DB_PORT`, `DB_USER`, `DB_NAME`, and `PGPASSWORD` instead of +`DATABASE_URL`. Check the job's logs as part of deployment verification. + +## API compatibility and limits + +The public GraphQL schema remains compatible with `main`, including nullable +legacy arguments and the Boolean `deleteRule` result. Missing arguments and null +room entries are rejected before database access. Database models are unchanged. +Calendar moves +use the additional `moveEvent` mutation, which updates only dates and rejects a +move when another user has already changed its original times. Members can still +edit shared bookings; replacing a stored contact number requires its owner or an +administrator. Omit `phone` or send `null` to retain the existing contact. +Hidden phone numbers are returned as empty strings. Legacy `User.sid` and +`User.jti` fields remain queryable but are deprecated and always return `null`; +session and token identifiers are not exposed. + +Date-range queries and booking durations are limited to 366 days. Recurring rules +can still span multiple years; each query expands only the requested period, with +a maximum of 10,000 rule occurrences per room. Excessive or invalid ranges return +a validation error; existing stored bookings and rules are not deleted or migrated. + Run `pnpm check`, `pnpm test` and `pnpm build` for the project checks. See [browser tests](e2e/README.md) for Playwright setup and CI image testing. diff --git a/bookit/src/auth/user.ts b/bookit/src/auth/user.ts index c30cb42..5fe4800 100644 --- a/bookit/src/auth/user.ts +++ b/bookit/src/auth/user.ts @@ -31,6 +31,9 @@ export function authenticatedUser(req: Request): User { sub: claims.sub, cid: claims.cid, name: claim("name"), + given_name: claim("given_name"), + family_name: claim("family_name"), + picture: claim("picture"), nickname: claim("nickname"), locale: claim("locale"), groups: Array.isArray(groups) diff --git a/bookit/src/generated/schema.ts b/bookit/src/generated/schema.ts index f6fdb06..21283c0 100644 --- a/bookit/src/generated/schema.ts +++ b/bookit/src/generated/schema.ts @@ -28,7 +28,7 @@ export type Event = { created_at?: Maybe<Scalars['String']['output']>; updated_at?: Maybe<Scalars['String']['output']>; room: Array<Room>; - phone?: Maybe<Scalars['String']['output']>; + phone: Scalars['String']['output']; booked_as: Scalars['String']['output']; booked_by: Scalars['String']['output']; }; @@ -46,7 +46,7 @@ export type Query = { export type QueryEventArgs = { - id: Scalars['String']['input']; + id?: InputMaybe<Scalars['String']['input']>; }; @@ -63,7 +63,7 @@ export type QueryIllegalSlotsArgs = { export type QueryRuleArgs = { - id: Scalars['String']['input']; + id?: InputMaybe<Scalars['String']['input']>; }; export type InputEvent = { @@ -74,7 +74,7 @@ export type InputEvent = { title: Scalars['String']['input']; created_at?: InputMaybe<Scalars['String']['input']>; updated_at?: InputMaybe<Scalars['String']['input']>; - room: Array<Room>; + room: Array<InputMaybe<Room>>; phone?: InputMaybe<Scalars['String']['input']>; booked_as: Scalars['String']['input']; booking_terms: Scalars['Boolean']['input']; @@ -84,34 +84,44 @@ export type Mutation = { __typename?: 'Mutation'; createEvent?: Maybe<Error>; editEvent?: Maybe<Error>; + moveEvent?: Maybe<Error>; deleteEvent?: Maybe<Error>; createRule?: Maybe<Error>; - deleteRule?: Maybe<Error>; + deleteRule?: Maybe<Scalars['Boolean']['output']>; }; export type MutationCreateEventArgs = { - event: InputEvent; + event?: InputMaybe<InputEvent>; }; export type MutationEditEventArgs = { - event: InputEvent; + event?: InputMaybe<InputEvent>; }; -export type MutationDeleteEventArgs = { +export type MutationMoveEventArgs = { id: Scalars['String']['input']; + start: Scalars['String']['input']; + end: Scalars['String']['input']; + previousStart: Scalars['String']['input']; + previousEnd: Scalars['String']['input']; +}; + + +export type MutationDeleteEventArgs = { + id?: InputMaybe<Scalars['String']['input']>; }; export type MutationCreateRuleArgs = { - rule: InputRule; + rule?: InputMaybe<InputRule>; }; export type MutationDeleteRuleArgs = { - id: Scalars['String']['input']; + id?: InputMaybe<Scalars['String']['input']>; }; export type IllegalSlot = { @@ -159,11 +169,18 @@ export type InputRule = { title: Scalars['String']['input']; created_at?: InputMaybe<Scalars['String']['input']>; updated_at?: InputMaybe<Scalars['String']['input']>; - room: Array<Room>; + room: Array<InputMaybe<Room>>; }; export type User = { __typename?: 'User'; + /** @deprecated Session identifiers are not exposed */ + sid?: Maybe<Scalars['String']['output']>; + /** @deprecated Token identifiers are not exposed */ + jti?: Maybe<Scalars['String']['output']>; + given_name?: Maybe<Scalars['String']['output']>; + family_name?: Maybe<Scalars['String']['output']>; + picture?: Maybe<Scalars['String']['output']>; sub?: Maybe<Scalars['String']['output']>; name?: Maybe<Scalars['String']['output']>; nickname?: Maybe<Scalars['String']['output']>; @@ -291,27 +308,28 @@ export type EventResolvers<ContextType = Context, ParentType extends ResolversPa created_at?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>; updated_at?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>; room?: Resolver<Array<ResolversTypes['Room']>, ParentType, ContextType>; - phone?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>; + phone?: Resolver<ResolversTypes['String'], ParentType, ContextType>; booked_as?: Resolver<ResolversTypes['String'], ParentType, ContextType>; booked_by?: Resolver<ResolversTypes['String'], ParentType, ContextType>; }; export type QueryResolvers<ContextType = Context, ParentType extends ResolversParentTypes['Query'] = ResolversParentTypes['Query']> = { events?: Resolver<Maybe<Array<Maybe<ResolversTypes['Event']>>>, ParentType, ContextType>; - event?: Resolver<Maybe<ResolversTypes['Event']>, ParentType, ContextType, RequireFields<QueryEventArgs, 'id'>>; + event?: Resolver<Maybe<ResolversTypes['Event']>, ParentType, ContextType, Partial<QueryEventArgs>>; eventsFT?: Resolver<Maybe<Array<Maybe<ResolversTypes['Event']>>>, ParentType, ContextType, RequireFields<QueryEventsFtArgs, 'from' | 'to'>>; illegalSlots?: Resolver<Maybe<Array<ResolversTypes['IllegalSlot']>>, ParentType, ContextType, RequireFields<QueryIllegalSlotsArgs, 'from' | 'to'>>; rules?: Resolver<Maybe<Array<Maybe<ResolversTypes['Rule']>>>, ParentType, ContextType>; - rule?: Resolver<Maybe<ResolversTypes['Rule']>, ParentType, ContextType, RequireFields<QueryRuleArgs, 'id'>>; + rule?: Resolver<Maybe<ResolversTypes['Rule']>, ParentType, ContextType, Partial<QueryRuleArgs>>; user?: Resolver<Maybe<ResolversTypes['User']>, ParentType, ContextType>; }; export type MutationResolvers<ContextType = Context, ParentType extends ResolversParentTypes['Mutation'] = ResolversParentTypes['Mutation']> = { - createEvent?: Resolver<Maybe<ResolversTypes['Error']>, ParentType, ContextType, RequireFields<MutationCreateEventArgs, 'event'>>; - editEvent?: Resolver<Maybe<ResolversTypes['Error']>, ParentType, ContextType, RequireFields<MutationEditEventArgs, 'event'>>; - deleteEvent?: Resolver<Maybe<ResolversTypes['Error']>, ParentType, ContextType, RequireFields<MutationDeleteEventArgs, 'id'>>; - createRule?: Resolver<Maybe<ResolversTypes['Error']>, ParentType, ContextType, RequireFields<MutationCreateRuleArgs, 'rule'>>; - deleteRule?: Resolver<Maybe<ResolversTypes['Error']>, ParentType, ContextType, RequireFields<MutationDeleteRuleArgs, 'id'>>; + createEvent?: Resolver<Maybe<ResolversTypes['Error']>, ParentType, ContextType, Partial<MutationCreateEventArgs>>; + editEvent?: Resolver<Maybe<ResolversTypes['Error']>, ParentType, ContextType, Partial<MutationEditEventArgs>>; + moveEvent?: Resolver<Maybe<ResolversTypes['Error']>, ParentType, ContextType, RequireFields<MutationMoveEventArgs, 'id' | 'start' | 'end' | 'previousStart' | 'previousEnd'>>; + deleteEvent?: Resolver<Maybe<ResolversTypes['Error']>, ParentType, ContextType, Partial<MutationDeleteEventArgs>>; + createRule?: Resolver<Maybe<ResolversTypes['Error']>, ParentType, ContextType, Partial<MutationCreateRuleArgs>>; + deleteRule?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType, Partial<MutationDeleteRuleArgs>>; }; export type IllegalSlotResolvers<ContextType = Context, ParentType extends ResolversParentTypes['IllegalSlot'] = ResolversParentTypes['IllegalSlot']> = { @@ -339,6 +357,11 @@ export type RuleResolvers<ContextType = Context, ParentType extends ResolversPar }; export type UserResolvers<ContextType = Context, ParentType extends ResolversParentTypes['User'] = ResolversParentTypes['User']> = { + sid?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>; + jti?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>; + given_name?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>; + family_name?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>; + picture?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>; sub?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>; name?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>; nickname?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>; diff --git a/bookit/src/models/user.ts b/bookit/src/models/user.ts index e0f3de3..4581166 100644 --- a/bookit/src/models/user.ts +++ b/bookit/src/models/user.ts @@ -1,5 +1,8 @@ export interface UserInfo { sub: string; + given_name?: string; + family_name?: string; + picture?: string; name: string; nickname: string; locale: string; diff --git a/bookit/src/resolvers/event.resolver.ts b/bookit/src/resolvers/event.resolver.ts index 2b97db4..700bdef 100644 --- a/bookit/src/resolvers/event.resolver.ts +++ b/bookit/src/resolvers/event.resolver.ts @@ -1,26 +1,37 @@ import { Tools } from "../utils/commonTypes"; import type { InputEvent } from "../generated/schema"; import { User } from "../models/user"; -import { createEvent, editEvent, deleteEvent } from "../services/event.service"; +import { + createEvent, + editEvent, + deleteEvent, + moveEvent, + type BookingMove, +} from "../services/event.service"; import { eventResult } from "./serialize"; +import { requiredInput, inputRooms } from "./input"; +import { queryRange } from "../utils/date-range"; export const getEventQResolvers = ({ prisma }: Tools) => ({ events: async () => { return (await prisma.event.findMany()).map(eventResult); }, eventsFT: async (_: unknown, ft: { from: string; to: string }) => { + const { from, to } = queryRange(ft.from, ft.to); + return ( await prisma.event.findMany({ where: { - end: { gte: new Date(ft.from) }, - start: { lte: new Date(ft.to) }, + end: { gte: from }, + start: { lte: to }, }, }) ).map(eventResult); }, - event: async (_: unknown, { id }: { id: string }) => { + event: async (_: unknown, { id }: { id?: string | null }) => { + const eventId = requiredInput(id, "Booking id"); const result = await prisma.event.findFirst({ - where: { id: id }, + where: { id: eventId }, }); return result ? eventResult(result) : null; @@ -28,13 +39,32 @@ export const getEventQResolvers = ({ prisma }: Tools) => ({ }); export const getEventMResolvers = ({ prisma }: Tools) => ({ - createEvent: async (_: unknown, { event }: { event: InputEvent }, { user }: { user: User }) => { - return createEvent(prisma, { ...event, booked_by: user.cid }, user); + createEvent: async ( + _: unknown, + { event }: { event?: InputEvent | null }, + { user }: { user: User }, + ) => { + const input = requiredInput(event, "Booking"); + + return createEvent( + prisma, + { ...input, room: inputRooms(input.room), booked_by: user.cid }, + user, + ); + }, + editEvent: async ( + _: unknown, + { event }: { event?: InputEvent | null }, + { user }: { user: User }, + ) => { + const input = requiredInput(event, "Booking"); + + return editEvent(prisma, { ...input, room: inputRooms(input.room), booked_by: user.cid }, user); }, - editEvent: async (_: unknown, { event }: { event: InputEvent }, { user }: { user: User }) => { - return editEvent(prisma, { ...event, booked_by: user.cid }, user); + moveEvent: async (_: unknown, move: BookingMove, { user }: { user: User }) => { + return moveEvent(prisma, move, user); }, - deleteEvent: async (_: unknown, { id }: { id: string }, { user }: { user: User }) => { - return deleteEvent(prisma, id, user); + deleteEvent: async (_: unknown, { id }: { id?: string | null }, { user }: { user: User }) => { + return deleteEvent(prisma, requiredInput(id, "Booking id"), user); }, }); diff --git a/bookit/src/resolvers/illegal_slots.resolver.ts b/bookit/src/resolvers/illegal_slots.resolver.ts index 1453813..f48db61 100644 --- a/bookit/src/resolvers/illegal_slots.resolver.ts +++ b/bookit/src/resolvers/illegal_slots.resolver.ts @@ -2,11 +2,11 @@ import { Tools } from "../utils/commonTypes"; import { room } from "@prisma/client"; import { slotResult } from "./serialize"; import { mergeRules, toExplicitRules, getRulesBetween } from "../services/rule.service"; +import { queryRange } from "../utils/date-range"; export const getIllegalSlotsQResolvers = ({ prisma }: Tools) => ({ illegalSlots: async (_: unknown, ft: { from: string; to: string }) => { - const from = new Date(ft.from); - const to = new Date(ft.to); + const { from, to } = queryRange(ft.from, ft.to); const rules = await getRulesBetween(prisma, from, to); diff --git a/bookit/src/resolvers/index.ts b/bookit/src/resolvers/index.ts index da8ecc2..925efa1 100644 --- a/bookit/src/resolvers/index.ts +++ b/bookit/src/resolvers/index.ts @@ -9,8 +9,9 @@ export const getResolvers = (tools: Tools) => { return { Event: { phone: (event, _args, { user }) => - user.is_admin || user.cid === event.booked_by ? (event.phone ?? null) : null, + user.is_admin || user.cid === event.booked_by ? (event.phone ?? "") : "", }, + User: { sid: () => null, jti: () => null }, Query: { ...getUserQResolvers(), ...getEventQResolvers(tools), diff --git a/bookit/src/resolvers/input.ts b/bookit/src/resolvers/input.ts new file mode 100644 index 0000000..9fca922 --- /dev/null +++ b/bookit/src/resolvers/input.ts @@ -0,0 +1,16 @@ +import { GraphQLError } from "graphql"; +import type { Room } from "../generated/schema"; + +// Keep legacy nullable GraphQL arguments while rejecting missing data before +// it reaches Prisma (where an undefined filter could select another record). +export function requiredInput<T>(value: T | null | undefined, name: string): T { + if (value == null) { + throw new GraphQLError(`${name} is required`, { extensions: { code: "BAD_USER_INPUT" } }); + } + + return value; +} + +export function inputRooms(rooms: (Room | null)[]): Room[] { + return rooms.map((room) => requiredInput(room, "Room")); +} diff --git a/bookit/src/resolvers/rule.resolver.ts b/bookit/src/resolvers/rule.resolver.ts index e902ddd..d07620d 100644 --- a/bookit/src/resolvers/rule.resolver.ts +++ b/bookit/src/resolvers/rule.resolver.ts @@ -2,16 +2,18 @@ import { User } from "../models"; import type { InputRule } from "../generated/schema"; import { createRule, deleteRule } from "../services/rule.service"; import { Tools } from "../utils/commonTypes"; +import { requiredInput, inputRooms } from "./input"; import { ruleResult } from "./serialize"; export const getRuleQResolvers = ({ prisma }: Tools) => ({ rules: async () => { return (await prisma.rule.findMany()).map(ruleResult); }, - rule: async (_: unknown, { id }: { id: string }) => { + rule: async (_: unknown, { id }: { id?: string | null }) => { + const ruleId = requiredInput(id, "Rule id"); const result = await prisma.rule.findUnique({ where: { - id: id, + id: ruleId, }, }); @@ -20,10 +22,16 @@ export const getRuleQResolvers = ({ prisma }: Tools) => ({ }); export const getRuleMResolvers = ({ prisma }: Tools) => ({ - createRule: async (_: unknown, { rule }: { rule: InputRule }, { user }: { user: User }) => { - return createRule(prisma, rule, user); + createRule: async ( + _: unknown, + { rule }: { rule?: InputRule | null }, + { user }: { user: User }, + ) => { + const input = requiredInput(rule, "Rule"); + + return createRule(prisma, { ...input, room: inputRooms(input.room) }, user); }, - deleteRule: async (_: unknown, { id }: { id: string }, { user }: { user: User }) => { - return deleteRule(prisma, id, user); + deleteRule: async (_: unknown, { id }: { id?: string | null }, { user }: { user: User }) => { + return (await deleteRule(prisma, requiredInput(id, "Rule id"), user)) === null; }, }); diff --git a/bookit/src/schemas/v1/event.gql b/bookit/src/schemas/v1/event.gql index 259d304..dc4c436 100644 --- a/bookit/src/schemas/v1/event.gql +++ b/bookit/src/schemas/v1/event.gql @@ -7,14 +7,14 @@ type Event { created_at: String updated_at: String room: [Room!]! - phone: String + phone: String! booked_as: String! booked_by: String! } type Query { events: [Event] - event(id: String!): Event + event(id: String): Event eventsFT(from: String!, to: String!): [Event] } @@ -26,14 +26,21 @@ input InputEvent { title: String! created_at: String updated_at: String - room: [Room!]! + room: [Room]! phone: String booked_as: String! booking_terms: Boolean! } type Mutation { - createEvent(event: InputEvent!): Error - editEvent(event: InputEvent!): Error - deleteEvent(id: String!): Error + createEvent(event: InputEvent): Error + editEvent(event: InputEvent): Error + moveEvent( + id: String! + start: String! + end: String! + previousStart: String! + previousEnd: String! + ): Error + deleteEvent(id: String): Error } diff --git a/bookit/src/schemas/v1/rule.gql b/bookit/src/schemas/v1/rule.gql index 810716c..a8b2d83 100644 --- a/bookit/src/schemas/v1/rule.gql +++ b/bookit/src/schemas/v1/rule.gql @@ -27,15 +27,15 @@ input InputRule { title: String! created_at: String updated_at: String - room: [Room!]! + room: [Room]! } type Query { rules: [Rule] - rule(id: String!): Rule + rule(id: String): Rule } type Mutation { - createRule(rule: InputRule!): Error - deleteRule(id: String!): Error + createRule(rule: InputRule): Error + deleteRule(id: String): Boolean } diff --git a/bookit/src/schemas/v1/user.gql b/bookit/src/schemas/v1/user.gql index c75fb4a..d2bd3c5 100644 --- a/bookit/src/schemas/v1/user.gql +++ b/bookit/src/schemas/v1/user.gql @@ -1,4 +1,9 @@ type User { + sid: String @deprecated(reason: "Session identifiers are not exposed") + jti: String @deprecated(reason: "Token identifiers are not exposed") + given_name: String + family_name: String + picture: String sub: String name: String nickname: String diff --git a/bookit/src/services/event.service.ts b/bookit/src/services/event.service.ts index f0799d2..2c965fb 100644 --- a/bookit/src/services/event.service.ts +++ b/bookit/src/services/event.service.ts @@ -3,8 +3,9 @@ import { Error, User } from "../models"; import type { InputEvent } from "../generated/schema"; import { checkRules } from "./rule.service"; import { setTimeout as delay } from "node:timers/promises"; +import { MAX_RANGE_DAYS, validRange } from "../utils/date-range"; -type Event = InputEvent & { booked_by: string }; +type Event = Omit<InputEvent, "room"> & { room: room[]; booked_by: string }; /* * Events must end after they start @@ -101,6 +102,13 @@ const validEvent = async ( }; } + if (!validRange(new Date(event.start), new Date(event.end))) { + return { + sv: `En bokning får vara högst ${MAX_RANGE_DAYS} dagar lång`, + en: `A booking may last at most ${MAX_RANGE_DAYS} days`, + }; + } + if (!isWithin9Weeks(event, user.is_admin)) { // 5443200000 = 63 days or 9 weeks return { @@ -260,7 +268,20 @@ export const editEvent = async ( }; } - // Never transfer access to a previous author's phone number to the editor. + if ( + previous.booked_by && + previous.booked_by !== user.cid && + !user.is_admin && + event.phone != null + ) { + return { + sv: "Endast kontaktpersonen eller en administratör får ändra telefonnumret", + en: "Only the contact owner or an administrator may change the phone number", + }; + } + + // Keeping a hidden number must also keep its owner. An anonymized booking + // needs a new contact, whose number is validated below. const updated = { ...event, phone: event.phone ?? (previous.booked_by ? previous.phone : ""), @@ -280,6 +301,69 @@ export const editEvent = async ( }); }; +export interface BookingMove { + id: string; + start: string; + end: string; + previousStart: string; + previousEnd: string; +} + +export const moveEvent = async ( + prisma: PrismaClient, + move: BookingMove, + user: User, +): Promise<Error | null> => { + return withBookingTransaction(prisma, async (transaction) => { + const previous = await transaction.event.findUnique({ where: { id: move.id } }); + + if (!previous) { + return { sv: "Kunde inte hämta gamla bokningen", en: "Failed to get event" }; + } + + if (!userIsInBookingGroup(previous, user)) { + return { + sv: "Du har inte behörighet att redigera denna bokning", + en: "You do not have permission to edit this event", + }; + } + + if ( + previous.start.getTime() !== Date.parse(move.previousStart) || + previous.end.getTime() !== Date.parse(move.previousEnd) + ) { + return { + sv: "Bokningens tid har ändrats. Ladda om kalendern och försök igen.", + en: "The booking time has changed. Reload the calendar and try again.", + }; + } + + const updated: Event = { + ...previous, + start: move.start, + end: move.end, + room: previous.room as room[], + booking_terms: true, + created_at: previous.created_at.toISOString(), + updated_at: previous.updated_at.toISOString(), + }; + + const error = + (await validEvent(transaction, updated, user)) || (await checkRules(transaction, updated)); + + if (error) { + return error; + } + + await transaction.event.update({ + where: { id: move.id }, + data: { start: new Date(move.start), end: new Date(move.end) }, + }); + + return null; + }); +}; + export const createEvent = async ( prisma: PrismaClient, event: Event, diff --git a/bookit/src/services/rule.service.ts b/bookit/src/services/rule.service.ts index 17232d9..cf331ac 100644 --- a/bookit/src/services/rule.service.ts +++ b/bookit/src/services/rule.service.ts @@ -1,7 +1,11 @@ import { Error, User } from "../models"; import type { InputEvent, InputRule } from "../generated/schema"; import { to } from "../utils"; -import { Prisma, rule } from "@prisma/client"; +import { Prisma, rule, room } from "@prisma/client"; +import { GraphQLError } from "graphql"; +import { validRange } from "../utils/date-range"; + +type RuleBooking = Omit<InputEvent, "room"> & { room: room[] }; /** * A single rule that applies to a specific time slot @@ -52,6 +56,12 @@ export const toExplicitRules = (rules: rule[], from: Date, to: Date): ExplicitRu return []; } + if (!validRange(from, to)) { + throw new GraphQLError("Choose a date range of at most 366 days.", { + extensions: { code: "BAD_USER_INPUT" }, + }); + } + for (const rule of rules) { const current = new Date(Math.max(from.getTime(), rule.start_date.getTime())); const end = new Date(Math.min(to.getTime(), rule.end_date.getTime())); @@ -61,6 +71,12 @@ export const toExplicitRules = (rules: rule[], from: Date, to: Date): ExplicitRu while (current <= end) { if (dayApplies(current, rule.day_mask)) { + if (explicitRules.length >= 10_000) { + throw new GraphQLError("Too many recurring rules. Choose a shorter date range.", { + extensions: { code: "BAD_USER_INPUT" }, + }); + } + insertRule(explicitRules, current, rule); } @@ -71,63 +87,52 @@ export const toExplicitRules = (rules: rule[], from: Date, to: Date): ExplicitRu return explicitRules.sort((a, b): number => a.priority - b.priority); }; -/** - * Returns a list of explicit rules that apply to the given event. - * If the rule to be inserted overlaps with the next rule in the list, - * the rule is split into two rules. The first rule is inserted into the list - * and the second rule is inserted into the list recursively. - */ -const mergeIntoList = ( - rule: ExplicitRule, - [nextMergedRule, ...mergedRules]: ExplicitRule[], -): ExplicitRule[] => { - if (rule.start >= rule.end) { - return nextMergedRule ? [nextMergedRule, ...mergedRules] : []; - } +// Input order defines precedence (toExplicitRules sorts by priority). Keep +// existing intervals and fill their gaps, without recursive array copying. +export const mergeRules = (rules: ExplicitRule[]): ExplicitRule[] => { + let merged: ExplicitRule[] = []; - if (nextMergedRule == undefined) { - return [rule]; - } + for (const rule of rules) { + let start = rule.start; - if (nextMergedRule.start > rule.start) { - if (nextMergedRule.start >= rule.end) { - return [rule, nextMergedRule, ...mergedRules]; + if (start >= rule.end) { + continue; } - return [ - { ...rule, end: new Date(nextMergedRule.start) }, - ...mergeIntoList({ ...rule, start: new Date(nextMergedRule.end) }, [ - nextMergedRule, - ...mergedRules, - ]), - ]; - } + const next: ExplicitRule[] = []; - if (nextMergedRule.end > rule.start) { - return [nextMergedRule, ...mergeIntoList({ ...rule, start: nextMergedRule.end }, mergedRules)]; - } + for (const existing of merged) { + if (start < existing.start && start < rule.end) { + const end = new Date(Math.min(existing.start.getTime(), rule.end.getTime())); - return [nextMergedRule, ...mergeIntoList(rule, mergedRules)]; -}; + next.push({ ...rule, start, end }); + } -export const mergeRules = (rules: ExplicitRule[]): ExplicitRule[] => { - let mergedRules: ExplicitRule[] = []; + next.push(existing); - for (const rule of rules) { - mergedRules = mergeIntoList(rule, mergedRules); + if (existing.end > start) { + start = existing.end; + } + } + + if (start < rule.end) { + next.push({ ...rule, start }); + } + + merged = next; } - return mergedRules; + return merged; }; -const breaksExplicitRule = (rule: ExplicitRule, event: InputEvent): boolean => { +const breaksExplicitRule = (rule: ExplicitRule, event: RuleBooking): boolean => { const start = new Date(event.start); const end = new Date(event.end); return rule.start < end && rule.end > start && !rule.allow; }; -export const doesObeyRules = (rules: rule[], event: InputEvent): Error | null => { +export const doesObeyRules = (rules: rule[], event: RuleBooking): Error | null => { const start = new Date(event.start); const end = new Date(event.end); @@ -167,7 +172,7 @@ export const getRulesBetween = async (prisma: Prisma.TransactionClient, from: Da }); }; -export const checkRules = async (prisma: Prisma.TransactionClient, event: InputEvent) => { +export const checkRules = async (prisma: Prisma.TransactionClient, event: RuleBooking) => { const rules = await prisma.rule.findMany({ where: { room: { @@ -190,7 +195,7 @@ const validDateTime = (start: Date, end: Date): boolean => { export const createRule = async ( prisma: Prisma.TransactionClient, - rule: InputRule, + rule: Omit<InputRule, "room"> & { room: room[] }, user: User, ): Promise<Error | null> => { if (!user.is_admin) { diff --git a/bookit/src/tests/event.test.ts b/bookit/src/tests/event.test.ts new file mode 100644 index 0000000..aaea8ad --- /dev/null +++ b/bookit/src/tests/event.test.ts @@ -0,0 +1,193 @@ +import { describe, expect, it, vi } from "vite-plus/test"; +import { Prisma, type PrismaClient, type event, type rule } from "@prisma/client"; +import { + createEvent, + editEvent, + moveEvent, + withBookingTransaction, +} from "../services/event.service"; +import { getResolvers } from "../resolvers"; +import { queryRange } from "../utils/date-range"; +import type { User, Error as BookingError } from "../models"; + +const owner: User = { + sub: "alice", + cid: "alice", + name: "Alice", + nickname: "Alice", + locale: "en", + groups: ["digit"], + is_admin: false, +}; +const member: User = { ...owner, sub: "bob", cid: "bob" }; +const admin: User = { ...member, is_admin: true }; + +function database(overrides: Partial<event> = {}) { + let saved: event = { + id: "00000000-0000-4000-8000-000000000001", + title: "Latest title", + description: "Latest instructions", + phone: "0701111111", + booked_by: owner.cid, + booked_as: "digit", + room: ["GROUP_ROOM"], + start: new Date(Date.now() + 86_400_000), + end: new Date(Date.now() + 90_000_000), + created_at: new Date(), + updated_at: new Date(), + ...overrides, + }; + const transaction = { + event: { + findUnique: vi.fn<() => Promise<event>>(async () => saved), + count: vi.fn<() => Promise<number>>(async () => 0), + update: vi.fn<(input: { data: Partial<event> }) => Promise<event>>( + async ({ data }) => (saved = { ...saved, ...data }), + ), + create: vi.fn<() => Promise<event>>(), + }, + rule: { findMany: vi.fn<() => Promise<rule[]>>(async () => []) }, + }; + const prisma = { + $transaction: vi.fn< + (operation: (tx: typeof transaction) => Promise<unknown>) => Promise<unknown> + >(async (operation) => operation(transaction)), + } as unknown as PrismaClient; + + const input = { + id: saved.id, + title: saved.title, + description: saved.description, + start: saved.start.toISOString(), + end: saved.end.toISOString(), + room: ["GROUP_ROOM"] as const, + booked_by: saved.booked_by, + booked_as: saved.booked_as, + booking_terms: true, + }; + + return { prisma, transaction, saved: () => saved, input: { ...input, room: [...input.room] } }; +} + +describe("booking contact access", () => { + it("keeps a hidden contact when a group member edits other fields", async () => { + const db = database(); + + expect( + await editEvent(db.prisma, { ...db.input, title: "Updated", phone: null }, member), + ).toBeNull(); + expect(db.saved()).toMatchObject({ title: "Updated", phone: "0701111111", booked_by: "alice" }); + const resolvePhone = getResolvers({ prisma: db.prisma }).Event.phone; + + expect(resolvePhone({ ...db.input, phone: db.saved().phone }, {}, { user: member })).toBe(""); + }); + + it("rejects a forged replacement number from another member", async () => { + const db = database(); + + expect( + (await editEvent(db.prisma, { ...db.input, phone: "0702222222" }, member))?.en, + ).toContain("contact owner"); + expect(db.transaction.event.update).not.toHaveBeenCalled(); + }); + + it.each([owner, admin])( + "allows the contact owner and administrators to replace a number ($cid, admin=$is_admin)", + async (user) => { + const db = database(); + + expect(await editEvent(db.prisma, { ...db.input, phone: "0702222222" }, user)).toBeNull(); + expect(db.saved()).toMatchObject({ phone: "0702222222", booked_by: "alice" }); + }, + ); + + it("requires a fresh contact when editing an anonymized booking", async () => { + const db = database({ booked_by: "", phone: "" }); + + expect(await editEvent(db.prisma, db.input, member)).not.toBeNull(); + expect(await editEvent(db.prisma, { ...db.input, phone: "0702222222" }, member)).toBeNull(); + expect(db.saved()).toMatchObject({ phone: "0702222222", booked_by: "bob" }); + }); +}); + +describe("calendar moves", () => { + it("preserves current metadata and contact details while changing only dates", async () => { + const db = database(); + const before = { ...db.saved() }; + const move = { + id: before.id, + previousStart: before.start.toISOString(), + previousEnd: before.end.toISOString(), + start: new Date(before.start.getTime() + 3_600_000).toISOString(), + end: new Date(before.end.getTime() + 3_600_000).toISOString(), + }; + + expect(await moveEvent(db.prisma, move, member)).toBeNull(); + expect(db.saved()).toEqual({ ...before, start: new Date(move.start), end: new Date(move.end) }); + expect((await moveEvent(db.prisma, move, member))?.en).toContain("time has changed"); + }); + + it("checks group access, overlaps, and recurring rules for moves", async () => { + const db = database(); + const previous = db.saved(); + const move = { + id: previous.id, + previousStart: previous.start.toISOString(), + previousEnd: previous.end.toISOString(), + start: previous.start.toISOString(), + end: previous.end.toISOString(), + }; + + expect((await moveEvent(db.prisma, move, { ...member, groups: [] }))?.en).toContain( + "permission", + ); + db.transaction.event.count.mockResolvedValue(1); + expect((await moveEvent(db.prisma, move, member))?.en).toContain("already taken"); + expect(db.transaction.event.update).not.toHaveBeenCalled(); + db.transaction.event.count.mockResolvedValue(0); + expect(await moveEvent(db.prisma, move, member)).toBeNull(); + expect(db.transaction.rule.findMany).toHaveBeenCalled(); + }); + + it("retries the complete operation after a serialization conflict", async () => { + const db = database(); + const operation = vi + .fn<() => Promise<BookingError | null>>() + .mockRejectedValueOnce( + new Prisma.PrismaClientKnownRequestError("conflict", { + code: "P2034", + clientVersion: "test", + }), + ) + .mockResolvedValue(null); + + expect(await withBookingTransaction(db.prisma, operation)).toBeNull(); + expect(operation).toHaveBeenCalledTimes(2); + }); +}); + +describe("date-range limits", () => { + it("rejects invalid and excessive queries before database work", () => { + for (const [from, to] of [ + ["invalid", "2026-01-01"], + ["2026-01-02", "2026-01-01"], + ["2026-01-01", "2040-01-01"], + ]) { + expect(() => queryRange(from, to)).toThrow("valid date range"); + } + + expect(() => queryRange("2024-01-01", "2025-01-01")).not.toThrow(); + }); + + it("rejects excessive booking durations before availability queries", async () => { + const db = database(); + const { id: _id, ...input } = db.input; + + expect( + (await createEvent(db.prisma, { ...input, phone: "0701111111", end: "2040-01-01" }, owner)) + ?.en, + ).toContain("366 days"); + expect(db.transaction.event.count).not.toHaveBeenCalled(); + expect(db.transaction.event.create).not.toHaveBeenCalled(); + }); +}); diff --git a/bookit/src/tests/fixtures/main-schema.graphql b/bookit/src/tests/fixtures/main-schema.graphql new file mode 100644 index 0000000..978b192 --- /dev/null +++ b/bookit/src/tests/fixtures/main-schema.graphql @@ -0,0 +1,124 @@ +# Public schema from main at 2378a27. Keep as the compatibility baseline. +type Error { + sv: String! + en: String! +} + +type Event { + id: String + start: String! + end: String! + description: String + title: String! + created_at: String + updated_at: String + room: [Room]! + phone: String! + booked_as: String! + booked_by: String! +} + +type Query { + events: [Event] + event(id: String): Event + eventsFT(from: String!, to: String!): [Event] +} + +input InputEvent { + id: String + start: String! + end: String! + description: String + title: String! + created_at: String + updated_at: String + room: [Room]! + phone: String! + booked_as: String! + booking_terms: Boolean! +} + +type Mutation { + createEvent(event: InputEvent): Error + editEvent(event: InputEvent): Error + deleteEvent(id: String): Error +} + +type IllegalSlot { + start: String! + end: String! + description: String + title: String! +} + +type Query { + illegalSlots(from: String!, to: String!): [IllegalSlot!] +} + +enum Room { + BIG_HUB + GROUP_ROOM + CTC + THE_CLOUD +} + +type Rule { + id: String + day_mask: Int + start_date: String + end_date: String + start_time: String + end_time: String + description: String + allow: Boolean + priority: Int + title: String + created_at: String + updated_at: String + room: [Room] +} + +input InputRule { + id: String + day_mask: Int + start_date: String! + end_date: String! + start_time: String! + end_time: String! + description: String! + allow: Boolean + priority: Int + title: String! + created_at: String + updated_at: String + room: [Room]! +} + +type Query { + rules: [Rule] + rule(id: String): Rule +} + +type Mutation { + createRule(rule: InputRule): Error + deleteRule(id: String): Boolean +} + +type User { + sid: String + sub: String + given_name: String + locale: String + picture: String + name: String + nickname: String + family_name: String + jti: String + cid: String + groups: [String] + is_admin: Boolean +} + +type Query { + user: User +} diff --git a/bookit/src/tests/rule.test.ts b/bookit/src/tests/rule.test.ts index 823b907..237a6c4 100644 --- a/bookit/src/tests/rule.test.ts +++ b/bookit/src/tests/rule.test.ts @@ -10,7 +10,7 @@ import { ruleDateBounds, } from "../services/rule.service"; import type { InputEvent } from "../generated/schema"; -import { rule } from "@prisma/client"; +import { rule, room } from "@prisma/client"; const defaultRule: rule = { id: "", @@ -199,7 +199,7 @@ describe("Booking rule scope", () => { }); it("does not let permission in one room override restrictions in another", () => { - const booking: InputEvent = { + const booking: Omit<InputEvent, "room"> & { room: room[] } = { title: "Two rooms", start: "2021-08-20T11:00", end: "2021-08-20T12:00", @@ -459,3 +459,64 @@ describe("Merge rules", () => { assertExplicitRuleEqual(expected[3], got[3]); }); }); + +describe("Rule processing bounds and precedence", () => { + it("rejects unbounded expansion while allowing long-lived rules in a calendar window", () => { + const from = new Date("2026-01-01"); + const to = new Date("2040-01-01"); + const rule = { + ...defaultRule, + start_date: from, + end_date: to, + start_time: "08:00", + end_time: "17:00", + }; + + assert.throws(() => toExplicitRules([rule], from, to), /366 days/); + assert.equal(toExplicitRules([rule], from, new Date("2026-01-07")).length, 7); + assert.throws( + () => + toExplicitRules( + Array.from({ length: 10_001 }, () => rule), + from, + from, + ), + /Too many recurring rules/, + ); + }); + + it("preserves the first applicable rule for nested, touching, and disjoint intervals", () => { + const rules = Array.from({ length: 40 }, (_, index) => { + const start = (index * 17) % 80; + + return { + ...defaultExplicitRules, + title: String(index), + start: new Date(start * 1000), + end: new Date((start + 1 + ((index * 11) % 25)) * 1000), + allow: index % 2 === 0, + }; + }); + const merged = mergeRules(rules); + + for (let second = 0.5; second < 110; second++) { + const instant = new Date(second * 1000); + const expected = rules.find((rule) => rule.start <= instant && instant < rule.end); + const actual = merged.filter((rule) => rule.start <= instant && instant < rule.end); + + assert.equal(actual.length, expected ? 1 : 0); + assert.equal(actual[0]?.title, expected?.title); + assert.equal(actual[0]?.allow, expected?.allow); + } + }); + + it("merges thousands of disjoint intervals without overflowing the call stack", () => { + const rules = Array.from({ length: 5_000 }, (_, index) => ({ + ...defaultExplicitRules, + start: new Date(index * 2000), + end: new Date(index * 2000 + 1000), + })); + + assert.deepEqual(mergeRules(rules), rules); + }); +}); diff --git a/bookit/src/tests/schema-compatibility.test.ts b/bookit/src/tests/schema-compatibility.test.ts new file mode 100644 index 0000000..f7d4992 --- /dev/null +++ b/bookit/src/tests/schema-compatibility.test.ts @@ -0,0 +1,100 @@ +import { readFileSync, readdirSync } from "node:fs"; +import { resolve } from "node:path"; +import { describe, expect, it, vi } from "vite-plus/test"; +import { buildASTSchema, findBreakingChanges, graphql } from "graphql/index.js"; +import { mergeTypeDefs } from "@graphql-tools/merge"; +import { makeExecutableSchema } from "@graphql-tools/schema"; +import type { PrismaClient } from "@prisma/client"; +import { getResolvers } from "../resolvers"; +import type { User } from "../models"; + +const directory = resolve(__dirname, "../schemas/v1"); +const typeDefs = mergeTypeDefs( + readdirSync(directory) + .filter((file) => file.endsWith(".gql")) + .map((file) => readFileSync(resolve(directory, file), "utf8")), +); +const user: User = { + sub: "member", + cid: "member", + name: "Member", + nickname: "Member", + locale: "en", + groups: ["digit"], + is_admin: false, +}; + +describe("main API compatibility", () => { + it("has no breaking schema changes against the main baseline", () => { + const baseline = readFileSync(resolve(__dirname, "fixtures/main-schema.graphql"), "utf8"); + + expect( + findBreakingChanges(buildASTSchema(mergeTypeDefs(baseline)), buildASTSchema(typeDefs)), + ).toEqual([]); + }); + + it("accepts the legacy Boolean delete mutation and keeps authorization", async () => { + const findUnique = vi.fn<PrismaClient["rule"]["findUnique"]>(); + const schema = makeExecutableSchema({ + typeDefs, + resolvers: getResolvers({ prisma: { rule: { findUnique } } as unknown as PrismaClient }), + }); + const result = await graphql({ + schema, + source: "mutation Delete($id: String) { deleteRule(id: $id) }", + variableValues: { id: "rule" }, + contextValue: { user }, + }); + + expect(result.errors).toBeUndefined(); + expect(result.data?.deleteRule).toBe(false); + expect(findUnique).not.toHaveBeenCalled(); + }); + + it("rejects missing legacy arguments before querying or writing the database", async () => { + // No database methods are supplied: any accidental access fails this test. + const schema = makeExecutableSchema({ + typeDefs, + resolvers: getResolvers({ prisma: {} as PrismaClient }), + }); + + for (const source of [ + "{ event { id } }", + "{ rule { id } }", + "mutation { createEvent { en } }", + "mutation { editEvent { en } }", + "mutation { deleteEvent { en } }", + "mutation { createRule { en } }", + "mutation { deleteRule }", + 'mutation { createEvent(event: {start: "2026-09-07T08:00:00Z", end: "2026-09-07T09:00:00Z", title: "Test", room: [null], booked_as: "digit", booking_terms: true}) { en } }', + ]) { + const result = await graphql({ schema, source, contextValue: { user } }); + + expect(result.errors).toHaveLength(1); + expect(result.errors?.[0]?.extensions.code).toBe("BAD_USER_INPUT"); + } + }); + + it("keeps legacy profile queries valid without exposing session identifiers", async () => { + const schema = makeExecutableSchema({ + typeDefs, + resolvers: getResolvers({ prisma: {} as PrismaClient }), + }); + const result = await graphql({ + schema, + source: "{ user { sid jti given_name family_name picture } }", + contextValue: { + user: { ...user, sid: "private-session", jti: "private-token", given_name: "Given" }, + }, + }); + + expect(result.errors).toBeUndefined(); + expect(result.data?.user).toEqual({ + sid: null, + jti: null, + given_name: "Given", + family_name: null, + picture: null, + }); + }); +}); diff --git a/bookit/src/tests/session-store.test.ts b/bookit/src/tests/session-store.test.ts new file mode 100644 index 0000000..b5a8596 --- /dev/null +++ b/bookit/src/tests/session-store.test.ts @@ -0,0 +1,65 @@ +import { describe, expect, it, vi } from "vite-plus/test"; +import { createSessionStore, type SessionRedis } from "../auth/session-store"; + +describe("OIDC session storage", () => { + it("stores a bounded session without access or refresh tokens", async () => { + const client = { + get: vi.fn<SessionRedis["get"]>(), + set: vi.fn<SessionRedis["set"]>().mockResolvedValue("OK"), + del: vi.fn<SessionRedis["del"]>().mockResolvedValue(1), + }; + const store = createSessionStore(client); + const now = Math.floor(Date.now() / 1000); + const payload = { + header: { iat: now, uat: now, exp: now + 120 }, + cookie: { expires: (now + 120) * 1000, maxAge: 120_000 }, + data: { + token_type: "Bearer", + expires_at: String(now + 120), + id_token: "id-token", + access_token: "access-token", + refresh_token: "refresh-token", + groups: ["digit"], + is_admin: false, + sessionExpiresAt: now + 120, + }, + }; + + await new Promise<void>((resolve, reject) => + store.set("session", payload, (error) => (error ? reject(error) : resolve())), + ); + const call = client.set.mock.calls[0]!; + + expect(call[0]).toBe("bookit:oidc:session"); + expect(JSON.parse(call[1]).data).toEqual({ + id_token: "id-token", + groups: ["digit"], + is_admin: false, + sessionExpiresAt: now + 120, + }); + expect(call[2].EX).toBeGreaterThan(0); + expect(call[2].EX).toBeLessThanOrEqual(120); + }); + + it("treats missing and expired sessions as logged out and reports corrupt storage", async () => { + const client = { + get: vi.fn<SessionRedis["get"]>(), + set: vi.fn<SessionRedis["set"]>(), + del: vi.fn<SessionRedis["del"]>(), + }; + const store = createSessionStore(client); + const read = () => + new Promise((resolve, reject) => + store.get("session", (error, value) => (error ? reject(error) : resolve(value))), + ); + + client.get.mockResolvedValue(null); + expect(await read()).toBeNull(); + client.get.mockResolvedValue(JSON.stringify({ header: { exp: 0 }, data: {} })); + expect(await read()).toBeNull(); + client.get.mockResolvedValue("corrupt"); + await expect(read()).rejects.toThrow(SyntaxError); + client.get.mockRejectedValue(new Error("Redis unavailable")); + await expect(read()).rejects.toThrow("Redis unavailable"); + }); +}); diff --git a/bookit/src/utils/date-range.ts b/bookit/src/utils/date-range.ts new file mode 100644 index 0000000..afc019b --- /dev/null +++ b/bookit/src/utils/date-range.ts @@ -0,0 +1,27 @@ +import { GraphQLError } from "graphql"; + +// Includes leap years and every calendar view used by the application. +export const MAX_RANGE_DAYS = 366; +const maxRangeMilliseconds = MAX_RANGE_DAYS * 24 * 60 * 60 * 1000; + +export function validRange(from: Date, to: Date): boolean { + return ( + Number.isFinite(from.getTime()) && + Number.isFinite(to.getTime()) && + from <= to && + to.getTime() - from.getTime() <= maxRangeMilliseconds + ); +} + +export function queryRange(from: string, to: string) { + const start = new Date(from); + const end = new Date(to); + + if (!validRange(start, end)) { + throw new GraphQLError(`Choose a valid date range of at most ${MAX_RANGE_DAYS} days.`, { + extensions: { code: "BAD_USER_INPUT" }, + }); + } + + return { from: start, to: end }; +} diff --git a/compose.cleanup.yml b/compose.cleanup.yml new file mode 100644 index 0000000..546c076 --- /dev/null +++ b/compose.cleanup.yml @@ -0,0 +1,7 @@ +# Standalone production cleanup. Use the same database settings as BookIT. +services: + db-scripts: + build: ./db-scripts + restart: unless-stopped + environment: + DATABASE_URL: ${DATABASE_URL:?Set DATABASE_URL to the BookIT database} diff --git a/db-scripts/cleanup.sh b/db-scripts/cleanup.sh index 225b333..c51724f 100644 --- a/db-scripts/cleanup.sh +++ b/db-scripts/cleanup.sh @@ -1,10 +1,23 @@ #!/bin/sh set -eu trap 'exit 0' INT TERM + +if [ -n "${DATABASE_URL:-}" ]; then + set -- "$DATABASE_URL" +else + set -- -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" +fi + while :; do - if psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -f /cleanup/delete-data.sql; then + if psql -v ON_ERROR_STOP=1 "$@" -f /cleanup/delete-data.sql; then + if [ "${CLEANUP_ONCE:-}" = 1 ]; then + exit 0 + fi delay=86400 else + if [ "${CLEANUP_ONCE:-}" = 1 ]; then + exit 1 + fi echo "BookIT privacy cleanup failed; retrying in one hour" >&2 delay=3600 fi diff --git a/docker-compose.yml b/docker-compose.yml index ed13881..25175f7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,6 +24,19 @@ services: timeout: 5s retries: 20 + db-scripts: + build: ./db-scripts + restart: unless-stopped + depends_on: + db: + condition: service_healthy + environment: + PGPASSWORD: password + DB_USER: postgres + DB_HOST: db + DB_PORT: 5432 + DB_NAME: bookit + gamma-db: image: postgres:16.0-alpine environment: diff --git a/e2e/README.md b/e2e/README.md index 6b84239..093708d 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -41,5 +41,20 @@ CI publishes a commit image after quality checks and runs this same target with image E2E requires a reviewed branch in this repository. GitHub releases retag the tested commit image without rebuilding. +Development and E2E share `gamma/users.json`. Development uses a stable SQL-seeded +OAuth client; E2E provisions a fresh client through Gamma's UI because each run +uses different ports and credentials. + Service versions are pinned in `compose.ts`. BookIT's PostgreSQL 12 and Redis 5 upgrades remain separate follow-ups. + +Manual verification with the pinned Gamma 2.5.1 image found that BookIT's OIDC +sign-out redirect reaches a Gamma HTTP 500 page. Gamma's own Logout button works; +use it before switching test users. The automated suite uses isolated browser +contexts and does not cover this sign-out flow. Resolve and retest the provider's +OIDC logout support before treating local sign-out as verified. + +The existing Prisma schema also gives rules a fixed February 2022 default for +`created_at` and `updated_at`. Newly created rules consequently show that date in +their details. Correcting those database defaults and defining update-time +behavior requires a separate migration; this change leaves the schema intact. diff --git a/e2e/booking-lifecycle.spec.ts b/e2e/booking-lifecycle.spec.ts index fd39709..5676141 100644 --- a/e2e/booking-lifecycle.spec.ts +++ b/e2e/booking-lifecycle.spec.ts @@ -1,5 +1,50 @@ import { test, expect } from "./fixtures"; import { acceptConditions, createBooking, fillBooking, openBooking } from "./booking-helpers"; +import { expectSegments } from "./date-time-helpers"; + +test("a new-booking link accepts UTC timestamps and preserves their local time", async ({ + page, +}) => { + const initial = await page.evaluate(() => { + const date = new Date(); + + date.setHours(12, 0, 0, 0); + + return { + start: date.toISOString(), + end: new Date(date.getTime() + 3_600_000).toISOString(), + year: date.getFullYear(), + month: date.getMonth() + 1, + day: date.getDate(), + }; + }); + + await page.goto(`/new-event?${new URLSearchParams({ start: initial.start, end: initial.end })}`); + await expect(page.getByRole("heading", { name: "New booking", exact: true })).toBeVisible(); + const { year, month, day } = initial; + + await expectSegments(page.getByRole("group", { name: "Begins at", exact: true }), { + year, + month, + day, + hour: 12, + minute: 0, + }); + await expectSegments(page.getByRole("group", { name: "Ends at", exact: true }), { + year, + month, + day, + hour: 13, + minute: 0, + }); + await page.getByRole("textbox", { name: "Title", exact: true }).fill("E2E UTC link booking"); + await page.getByRole("textbox", { name: "Phone number", exact: true }).fill("0701234567"); + await page.getByRole("combobox", { name: "Booking as", exact: true }).selectOption("digit"); + await acceptConditions(page); + await page.getByRole("button", { name: "Save booking", exact: true }).click(); + await expect(page).toHaveURL(/\/$/); + await openBooking(page, "E2E UTC link booking"); +}); test("a group member creates, edits and deletes a persisted booking", async ({ page }) => { const title = "E2E digIT planning"; diff --git a/e2e/calendar.spec.ts b/e2e/calendar.spec.ts index e57e1e2..5e90732 100644 --- a/e2e/calendar.spec.ts +++ b/e2e/calendar.spec.ts @@ -191,6 +191,45 @@ test("confirming a calendar drag preserves the confirmed times when editing", as ).toBeVisible(); }); +test("a move confirmed after another editor saves preserves their newer details", async ({ + page, +}) => { + const title = "E2E stale calendar"; + + await createBooking(page, title); + await openBooking(page, title); + await page.getByRole("button", { name: "Edit", exact: true }).click(); + await expect(page.getByRole("textbox", { name: "Title", exact: true })).toBeVisible(); + const editUrl = page.url(); + + await page.goto("/"); + await dragBooking(page, title); + const confirmation = page.getByRole("dialog", { name: "Move booking?", exact: true }); + + await expect(confirmation).toBeVisible(); + const editor = await page.context().newPage(); + + try { + await editor.goto(editUrl); + await editor.getByRole("textbox", { name: "Title", exact: true }).fill(`${title} updated`); + await editor + .getByRole("textbox", { name: "Description", exact: true }) + .fill("New instructions from the other editor"); + await editor.getByRole("button", { name: "Save booking", exact: true }).click(); + await expect(editor).toHaveURL(/\/$/); + + await confirmation.getByRole("button", { name: "Move booking", exact: true }).click(); + await expect(page.getByText("Booking moved", { exact: true })).toBeVisible(); + await page.reload(); + await openBooking(page, `${title} updated`); + await expect( + page.getByText("New instructions from the other editor", { exact: true }), + ).toBeVisible(); + } finally { + await editor.close(); + } +}); + test("a rejected calendar move keeps the original booking times", async ({ page }) => { const title = "E2E rejected move"; diff --git a/e2e/cleanup.spec.ts b/e2e/cleanup.spec.ts new file mode 100644 index 0000000..20c0fc8 --- /dev/null +++ b/e2e/cleanup.spec.ts @@ -0,0 +1,52 @@ +import { test, expect } from "./fixtures"; +import { createBooking, fillBooking, openBooking } from "./booking-helpers"; +import { dateSegments, fillSegments } from "./date-time-helpers"; + +test("scheduled cleanup removes old contact data while retaining current bookings", async ({ + page, + environment, +}) => { + await createBooking(page, "E2E current contact"); + await page.getByRole("link", { name: "New booking", exact: true }).click(); + await fillBooking(page, "E2E old contact"); + const oldDate = await page.evaluate(() => { + const date = new Date(); + + date.setDate(date.getDate() - 21); + + return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`; + }); + + for (const name of ["Begins at", "Ends at"]) { + await fillSegments(page.getByRole("group", { name, exact: true }), dateSegments(oldDate)); + } + + await page.getByRole("button", { name: "Save booking", exact: true }).click(); + await expect(page).toHaveURL(/\/$/); + await environment.cleanupPersonalData(); + await page.reload(); + await openBooking(page, "E2E current contact"); + await page.getByRole("button", { name: "Edit", exact: true }).click(); + await expect(page.getByRole("textbox", { name: "Phone number", exact: true })).toHaveValue( + "0701234567", + ); + + await page.goto("/"); + + for (let week = 0; week < 3; week++) { + await page.getByRole("button", { name: /previous/i }).click(); + } + + await openBooking(page, "E2E old contact"); + const details = page.getByRole("dialog", { name: "Booking details", exact: true }); + + await expect( + details.getByText("Booked by", { exact: true }).locator("..").locator("dd"), + ).toHaveText("—"); + await page.getByRole("button", { name: "Edit", exact: true }).click(); + await expect(page.getByRole("textbox", { name: "Phone number", exact: true })).toHaveValue(""); + await expect(page.getByRole("textbox", { name: "Phone number", exact: true })).toHaveAttribute( + "required", + "", + ); +}); diff --git a/e2e/compose.ts b/e2e/compose.ts index e559be1..e61ebbc 100644 --- a/e2e/compose.ts +++ b/e2e/compose.ts @@ -2,6 +2,8 @@ import { expect, type Browser, type Page } from "@playwright/test"; import { PostgreSqlContainer, type StartedPostgreSqlContainer } from "@testcontainers/postgresql"; import { GenericContainer, Network, Wait, type StartedTestContainer } from "testcontainers"; import { createServer, type AddressInfo } from "node:net"; +import seed from "../gamma/users.json"; +import { resolve } from "node:path"; export const images = { bookitPostgres: "postgres:12.22-alpine", @@ -12,62 +14,32 @@ export const images = { "ghcr.io/cthit/gamma:2.5.1@sha256:5112c5673ee5c98b072c38afe4ed0de5f7f7d6ae85c41bbd318c92f0e3d5d9db", } as const; +function userWithCid(cid: string) { + const user = seed.users.find((user) => user.cid === cid); + + if (!user) { + throw new Error(`Missing Gamma fixture: ${cid}`); + } + + return user; +} + const users = { - admin: { - id: "88eec5c2-5ebb-4e13-9a76-fcc4dac9e74f", - cid: "bookadmin", - nick: "BookIT Admin", - groups: ["digit"], - }, - member: { - id: "bc605869-9a4d-46ec-8a29-d00819d4c195", - cid: "bookmember", - nick: "BookIT Member", - groups: ["digit"], - }, - outsider: { - id: "ec8987d7-4087-461d-bed5-9365086b6e3b", - cid: "bookguest", - nick: "BookIT Guest", - groups: [], - }, -} as const; + admin: userWithCid("bookadmin"), + member: userWithCid("bookmember"), + outsider: userWithCid("bookguest"), +}; export type UserRole = keyof typeof users; const testPassword = "password1337"; -const groupId = "aed27030-ad90-4526-855c-1e909b1dcecb"; - -const postId = "7bb1db15-730d-4864-bfc3-99abe7c0ccf8"; - -const seed = { - users: Object.values(users).map((user) => ({ - id: user.id, - cid: user.cid, - nick: user.nick, - firstName: "BookIT", - lastName: user.cid, - acceptanceYear: 2020, - })), - superGroups: [{ id: groupId, name: "digit", prettyName: "digIT", type: "COMMITTEE" }], - groups: [ - { - id: "acd27030-ad90-4526-855c-1e909b1dcecb", - name: "digit-test", - prettyName: "digIT test", - superGroupId: groupId, - members: [users.admin, users.member].map((user) => ({ userId: user.id, postId })), - }, - ], - posts: [{ id: postId, postName: { sv: "Testmedlem", en: "Test member" } }], -}; - export interface Environment { appUrl: string; gammaUrl: string; logs: Record<string, string>; resetBookings(): Promise<void>; + cleanupPersonalData(): Promise<void>; stop(): Promise<void>; } @@ -362,6 +334,33 @@ export async function compose(browser: Browser): Promise<Environment> { logs, stop, + async cleanupPersonalData() { + await bookitDb.copyFilesToContainer([ + { source: resolve("db-scripts/cleanup.sh"), target: "/cleanup/cleanup.sh" }, + { source: resolve("db-scripts/delete-data.sql"), target: "/cleanup/delete-data.sql" }, + ]); + + // Exercise both supported deployment configurations against real data. + for (const connection of [ + { DATABASE_URL: "postgresql://bookit_test:bookit_test@127.0.0.1:5432/bookit_test" }, + { + DB_HOST: "127.0.0.1", + DB_PORT: "5432", + DB_USER: "bookit_test", + DB_NAME: "bookit_test", + PGPASSWORD: "bookit_test", + }, + ]) { + const result = await bookitDb.exec(["sh", "/cleanup/cleanup.sh"], { + env: { ...connection, CLEANUP_ONCE: "1" }, + }); + + if (result.exitCode !== 0) { + throw new Error(`Privacy cleanup failed: ${result.output}`); + } + } + }, + async resetBookings() { const result = await bookitDb.exec([ "psql", diff --git a/e2e/permissions.spec.ts b/e2e/permissions.spec.ts index dd96156..f03c3c4 100644 --- a/e2e/permissions.spec.ts +++ b/e2e/permissions.spec.ts @@ -64,3 +64,31 @@ test("an outsider can view bookings but cannot access editing or administration" await expect(page.getByRole("button", { name: "Edit", exact: true })).toHaveCount(0); await expect(page.getByRole("button", { name: "Delete", exact: true })).toHaveCount(0); }); + +test("a group member can edit a colleague's booking without replacing their private contact", async ({ + page, + environment, +}) => { + await loginAs(page, environment, "admin"); + await createBooking(page, "E2E colleague contact"); + await loginAs(page, environment, "member"); + await openBooking(page, "E2E colleague contact"); + await page.getByRole("button", { name: "Edit", exact: true }).click(); + await expect(page.getByRole("textbox", { name: "Phone number", exact: true })).toBeDisabled(); + await expect(page.getByRole("textbox", { name: "Phone number", exact: true })).toHaveValue(""); + await page + .getByRole("textbox", { name: "Description", exact: true }) + .fill("Edited with the original contact preserved"); + await page.getByRole("button", { name: "Save booking", exact: true }).click(); + await expect(page).toHaveURL(/\/$/); + + await loginAs(page, environment, "admin"); + await openBooking(page, "E2E colleague contact"); + await expect( + page.getByText("Edited with the original contact preserved", { exact: true }), + ).toBeVisible(); + await page.getByRole("button", { name: "Edit", exact: true }).click(); + await expect(page.getByRole("textbox", { name: "Phone number", exact: true })).toHaveValue( + "0701234567", + ); +}); diff --git a/frontend/src/api/operations.graphql b/frontend/src/api/operations.graphql index ac27f68..14f0955 100644 --- a/frontend/src/api/operations.graphql +++ b/frontend/src/api/operations.graphql @@ -57,6 +57,25 @@ mutation UpdateBooking($event: InputEvent!) { } } +mutation MoveBooking( + $id: String! + $start: String! + $end: String! + $previousStart: String! + $previousEnd: String! +) { + moveEvent( + id: $id + start: $start + end: $end + previousStart: $previousStart + previousEnd: $previousEnd + ) { + en + sv + } +} + mutation DeleteBooking($id: String!) { deleteEvent(id: $id) { en @@ -90,8 +109,5 @@ mutation CreateRule($rule: InputRule!) { } mutation DeleteRule($id: String!) { - deleteRule(id: $id) { - en - sv - } + deleteRule(id: $id) } diff --git a/frontend/src/components/booking-form.tsx b/frontend/src/components/booking-form.tsx index 0b643ed..33bb2a1 100644 --- a/frontend/src/components/booking-form.tsx +++ b/frontend/src/components/booking-form.tsx @@ -55,6 +55,7 @@ export function BookingForm({ const [error, setError] = useState(""); const [defaultStart] = useState(() => new Date()); const groups = user?.groups?.filter((group): group is string => Boolean(group)) ?? []; + const canEditPhone = !booking?.booked_by || booking.booked_by === user?.cid || user?.is_admin; const eligibleGroups = user?.is_admin && booking && !groups.includes(booking.booked_as) @@ -159,6 +160,7 @@ export function BookingForm({ name="phone" type="tel" autoComplete="tel" + disabled={!canEditPhone} required={!booking?.booked_by} pattern={String.raw`\+?\(?[0-9]{3}\)?[\-\s.]?[0-9]{3}[\-\s.]?[0-9]{4,5}`} defaultValue={booking?.phone ?? ""} @@ -168,6 +170,14 @@ export function BookingForm({ : "070 123 45 67" } /> + {!canEditPhone && ( + <p className="text-sm text-muted-foreground"> + {t( + "The contact number is kept private. Its owner or an administrator can change it.", + "Kontaktens nummer är privat. Kontaktpersonen eller en administratör kan ändra det.", + )} + </p> + )} </div> <div className="field"> <Label htmlFor="booked_as">{t("Booking as", "Bokas som")}</Label> diff --git a/frontend/src/components/event-calendar-toolbar.tsx b/frontend/src/components/event-calendar-toolbar.tsx index 4daa17d..14261c9 100644 --- a/frontend/src/components/event-calendar-toolbar.tsx +++ b/frontend/src/components/event-calendar-toolbar.tsx @@ -8,30 +8,18 @@ export interface EventCalendarToolbarProps { className?: string; controller: CalendarController; availableViews: string[]; - addButton?: { - isPrimary?: boolean; - text?: string; - hint?: string; - click?: () => void; - }; } export function EventCalendarToolbar({ className, controller, availableViews, - addButton, }: EventCalendarToolbarProps) { const buttons = controller.getButtonState(); return ( <div className={cn("flex items-center justify-between flex-wrap gap-3", className)}> <div className="flex items-center flex-wrap gap-3"> - {addButton && ( - <Button onClick={addButton.click} aria-label={addButton.hint}> - {addButton.text} - </Button> - )} <Button onClick={() => controller.today()} aria-label={buttons.today.hint} diff --git a/frontend/src/components/event-calendar.tsx b/frontend/src/components/event-calendar.tsx index dce2d54..d2b8973 100644 --- a/frontend/src/components/event-calendar.tsx +++ b/frontend/src/components/event-calendar.tsx @@ -2,49 +2,28 @@ import { type CalendarOptions, useCalendarController } from "@fullcalendar/react import dayGridPlugin from "@fullcalendar/react/daygrid"; import interactionPlugin from "@fullcalendar/react/interaction"; import listPlugin from "@fullcalendar/react/list"; -import multiMonthPlugin from "@fullcalendar/react/multimonth"; import timeGridPlugin from "@fullcalendar/react/timegrid"; import { EventCalendarToolbar } from "@/components/event-calendar-toolbar"; import { EventCalendarViews } from "@/components/ui/event-calendar-views"; import { EventCalendarCloseIcon } from "@/components/event-calendar-icons"; import { cn } from "@/lib/utils"; -const plugins = [dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin, multiMonthPlugin]; - -const defaultAvailableViews = [ - "dayGridMonth", - "timeGridWeek", - "timeGridDay", - "listWeek", - "multiMonthYear", -]; - -const navLinkDayClick = "timeGridDay"; - -const navLinkWeekClick = "timeGridWeek"; +const plugins = [dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin]; export interface EventCalendarProps extends Omit< CalendarOptions, - "class" | "className" | "headerToolbar" | "footerToolbar" + "class" | "className" | "headerToolbar" | "footerToolbar" | "plugins" > { className?: string; - availableViews?: string[]; - addButton?: { - isPrimary?: boolean; - text?: string; - hint?: string; - click?: () => void; - }; + availableViews: string[]; } export function EventCalendar({ - availableViews = defaultAvailableViews, - addButton, + availableViews, className, height, contentHeight, direction, - plugins: userPlugins = [], ...restOptions }: EventCalendarProps) { const controller = useCalendarController(); @@ -73,16 +52,15 @@ export function EventCalendar({ className="px-4 py-5 sm:px-5 gap-y-4" controller={controller} availableViews={availableViews} - addButton={addButton} /> <div className="grow min-h-0"> <EventCalendarViews controller={controller} height={isHeightAuto ? "auto" : height !== undefined ? "100%" : contentHeight} initialView={availableViews[0]} - navLinkDayClick={navLinkDayClick} - navLinkWeekClick={navLinkWeekClick} - plugins={[...plugins, ...userPlugins]} + navLinkDayClick="timeGridDay" + navLinkWeekClick="timeGridWeek" + plugins={plugins} popoverCloseContent={() => <EventCalendarCloseIcon />} {...restOptions} /> diff --git a/frontend/src/components/rule-details.tsx b/frontend/src/components/rule-details.tsx new file mode 100644 index 0000000..38ee553 --- /dev/null +++ b/frontend/src/components/rule-details.tsx @@ -0,0 +1,44 @@ +import { roomName } from "@/lib/rooms"; +import { useLanguage } from "@/lib/language"; +import { ruleDateText as dateText, ruleDayText, type RuleDetails as Rule } from "@/lib/rules"; +import { Button } from "@/components/ui/button"; +import { DialogFooter } from "@/components/ui/dialog"; + +export function RuleDetails({ details, onClose }: { details: Rule; onClose: () => void }) { + const { t, language } = useLanguage(); + const dayText = (mask: number | null | undefined) => ruleDayText(mask, language); + + return ( + <> + <dl className="grid gap-4 sm:grid-cols-2 text-sm"> + {[ + [t("Title", "Titel"), details.title], + [t("Priority", "Prioritet"), details.priority], + [t("Start date", "Startdatum"), dateText(details.start_date)], + [t("End date", "Slutdatum"), dateText(details.end_date)], + [t("Start time", "Starttid"), details.start_time], + [t("End time", "Sluttid"), details.end_time], + [t("Description", "Beskrivning"), details.description], + [t("Rooms", "Rum"), details.room?.map(roomName).join(", ")], + [t("Weekdays", "Veckodagar"), dayText(details.day_mask)], + [ + t("Availability", "Tillgänglighet"), + details.allow ? t("Allowed", "Tillåten") : t("Blocked", "Spärrad"), + ], + [t("Created", "Skapad"), dateText(details.created_at, true)], + [t("Updated", "Uppdaterad"), dateText(details.updated_at, true)], + ].map(([label, value]) => ( + <div key={label}> + <dt className="text-muted-foreground">{label}</dt> + <dd className="mt-1 whitespace-pre-wrap">{value ?? "—"}</dd> + </div> + ))} + </dl> + <DialogFooter> + <Button variant="outline" onClick={onClose}> + {t("Close", "Stäng")} + </Button> + </DialogFooter> + </> + ); +} diff --git a/frontend/src/components/rule-form.tsx b/frontend/src/components/rule-form.tsx new file mode 100644 index 0000000..0f69145 --- /dev/null +++ b/frontend/src/components/rule-form.tsx @@ -0,0 +1,158 @@ +import { useState, type FormEvent } from "react"; +import { useMutation } from "@tanstack/react-query"; +import { format } from "date-fns"; +import { toast } from "sonner"; +import { request, checkMutation } from "@/api/client"; +import { CreateRuleDocument, type Room } from "@/generated/graphql"; +import { formText } from "@/lib/forms"; +import { rooms } from "@/lib/rooms"; +import { useLanguage } from "@/lib/language"; +import { ruleWeekdays } from "@/lib/rules"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { Checkbox } from "@/components/ui/checkbox"; +import { DatePicker, TimePicker } from "@/components/date-time-fields"; +import { DialogFooter } from "@/components/ui/dialog"; + +export function RuleForm({ + onSaved, + onCancel, +}: { + onSaved: () => Promise<void>; + onCancel: () => void; +}) { + const { t, language } = useLanguage(); + const [selected, setSelected] = useState<Room[]>([]); + const [days, setDays] = useState(0); + const [error, setError] = useState(""); + const weekdays = ruleWeekdays[language]; + + const create = useMutation({ + mutationFn: async (form: FormData) => { + if (!selected.length || !days) { + throw new Error( + t("Select rooms and at least one weekday.", "Välj rum och minst en veckodag."), + ); + } + + const rule = { + title: formText(form, "title").trim(), + description: formText(form, "description"), + priority: Number(form.get("priority")), + allow: form.get("allow") === "true", + day_mask: days, + room: selected, + start_date: formText(form, "start_date"), + end_date: formText(form, "end_date"), + start_time: formText(form, "start_time").slice(0, 5), + end_time: formText(form, "end_time").slice(0, 5), + }; + + if (rule.end_date < rule.start_date || rule.end_time <= rule.start_time) { + throw new Error( + t("The rule must end after it starts.", "Regeln måste sluta efter att den börjar."), + ); + } + + const result = await request(CreateRuleDocument, { rule }); + + checkMutation(result.createRule, language); + }, + onSuccess: async () => { + await onSaved(); + toast.success(t("Rule created", "Regeln skapades")); + }, + onError: (reason) => setError(reason.message), + }); + + function submit(event: FormEvent<HTMLFormElement>) { + event.preventDefault(); + setError(""); + create.mutate(new FormData(event.currentTarget)); + } + + return ( + <form onSubmit={submit} className="space-y-5"> + <div className="field"> + <Label htmlFor="rule-title">{t("Title", "Titel")}</Label> + <Input id="rule-title" name="title" required maxLength={200} /> + </div> + <div className="grid grid-cols-2 gap-4"> + <div className="field"> + <Label htmlFor="priority">{t("Priority", "Prioritet")}</Label> + <Input id="priority" name="priority" type="number" required defaultValue={10} /> + </div> + <div className="field"> + <Label htmlFor="allow">{t("Availability", "Tillgänglighet")}</Label> + <select id="allow" name="allow" className="native-select"> + <option value="true">{t("Allowed", "Tillåten")}</option> + <option value="false">{t("Blocked", "Spärrad")}</option> + </select> + </div> + <DatePicker + label={t("Start date", "Startdatum")} + name="start_date" + defaultValue={format(new Date(), "yyyy-MM-dd")} + /> + <DatePicker label={t("End date", "Slutdatum")} name="end_date" defaultValue="2040-12-31" /> + <TimePicker label={t("Start time", "Starttid")} name="start_time" defaultValue="08:00" /> + <TimePicker label={t("End time", "Sluttid")} name="end_time" defaultValue="17:00" /> + </div> + <fieldset> + <legend className="text-sm font-medium mb-3">{t("Weekdays", "Veckodagar")}</legend> + <div className="flex flex-wrap gap-3"> + {weekdays.map((day, index) => ( + <label className="flex gap-2 items-center text-sm" key={day}> + <Checkbox + aria-label={day} + checked={Boolean(days & (1 << index))} + onCheckedChange={(checked) => + setDays(checked ? days | (1 << index) : days & ~(1 << index)) + } + /> + {day} + </label> + ))} + </div> + </fieldset> + <fieldset> + <legend className="text-sm font-medium mb-3">{t("Rooms", "Rum")}</legend> + <div className="grid grid-cols-2 gap-3"> + {rooms.map((room) => ( + <label className="flex gap-2 items-center text-sm" key={room.id}> + <Checkbox + aria-label={room.name} + checked={selected.includes(room.id)} + onCheckedChange={(checked) => + setSelected( + checked ? [...selected, room.id] : selected.filter((id) => id !== room.id), + ) + } + /> + {room.name} + </label> + ))} + </div> + </fieldset> + <div className="field"> + <Label htmlFor="rule-description">{t("Description", "Beskrivning")}</Label> + <Textarea id="rule-description" name="description" /> + </div> + {error && ( + <p role="alert" className="text-sm text-destructive"> + {error} + </p> + )} + <DialogFooter> + <Button type="button" variant="outline" onClick={onCancel}> + {t("Cancel", "Avbryt")} + </Button> + <Button type="submit" disabled={create.isPending}> + {t("Save rule", "Spara regel")} + </Button> + </DialogFooter> + </form> + ); +} diff --git a/frontend/src/components/ui/event-calendar-views.tsx b/frontend/src/components/ui/event-calendar-views.tsx index 6b5aff5..cc70e34 100644 --- a/frontend/src/components/ui/event-calendar-views.tsx +++ b/frontend/src/components/ui/event-calendar-views.tsx @@ -270,19 +270,6 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa } listDayBodyClass="grow min-w-0 py-3 pe-3 sm:pe-5 gap-2" - singleMonthClass={(info) => - cn( - info.multiMonthColumns > 1 && "m-4", - info.multiMonthColumns === 1 && !info.isLast && "border-b", - ) - } - singleMonthHeaderClass={(info) => - cn(info.multiMonthColumns > 1 ? "pb-2" : "py-1 border-b bg-background", "items-center") - } - singleMonthHeaderInnerClass={(info) => - cn("px-3 py-1 rounded-full text-base font-bold", info.hasNavLink && "hover:bg-foreground/5") - } - tableHeaderClass="bg-background" fillerClass={(info) => cn("opacity-50 border", info.inTableHeader && "border-transparent")} dayNarrowWidth={100} @@ -311,15 +298,6 @@ export function EventCalendarViews({ views: userViews, ...restOptions }: EventCa dayCellBottomClass: getShortDayCellBottomClass, ...userViews?.dayGrid, }, - multiMonth: { - ...dayRowCommonClasses, - dayCellBottomClass: getShortDayCellBottomClass, - tableBodyClass: (info) => - cn(info.multiMonthColumns > 1 && "border rounded-sm overflow-hidden"), - dayHeaderInnerClass: (info) => cn(!info.inPopover && "mb-2"), - dayHeaderDividerClass: (info) => cn(info.multiMonthColumns === 1 && "border-b"), - ...userViews?.multiMonth, - }, timeGrid: { ...dayRowCommonClasses, dayCellBottomClass: tallDayCellBottomClass, diff --git a/frontend/src/generated/graphql.ts b/frontend/src/generated/graphql.ts index 4d25b7f..100cf2d 100644 --- a/frontend/src/generated/graphql.ts +++ b/frontend/src/generated/graphql.ts @@ -11,7 +11,7 @@ export type InputEvent = { title: string; created_at?: string | null | undefined; updated_at?: string | null | undefined; - room: Array<Room>; + room: Array<Room | null | undefined>; phone?: string | null | undefined; booked_as: string; booking_terms: boolean; @@ -36,10 +36,10 @@ export type InputRule = { title: string; created_at?: string | null | undefined; updated_at?: string | null | undefined; - room: Array<Room>; + room: Array<Room | null | undefined>; }; -export type BookingFragment = { id: string | null, title: string, description: string | null, start: string, end: string, room: Array<Room>, phone: string | null, booked_as: string, booked_by: string, created_at: string | null, updated_at: string | null }; +export type BookingFragment = { id: string | null, title: string, description: string | null, start: string, end: string, room: Array<Room>, phone: string, booked_as: string, booked_by: string, created_at: string | null, updated_at: string | null }; export type CurrentUserQueryVariables = Exact<{ [key: string]: never; }>; @@ -52,14 +52,14 @@ export type CalendarQueryVariables = Exact<{ }>; -export type CalendarQuery = { eventsFT: Array<{ id: string | null, title: string, description: string | null, start: string, end: string, room: Array<Room>, phone: string | null, booked_as: string, booked_by: string, created_at: string | null, updated_at: string | null } | null> | null, illegalSlots: Array<{ start: string, end: string, title: string, description: string | null, room: Array<Room> }> | null }; +export type CalendarQuery = { eventsFT: Array<{ id: string | null, title: string, description: string | null, start: string, end: string, room: Array<Room>, phone: string, booked_as: string, booked_by: string, created_at: string | null, updated_at: string | null } | null> | null, illegalSlots: Array<{ start: string, end: string, title: string, description: string | null, room: Array<Room> }> | null }; export type BookingDetailQueryVariables = Exact<{ id: string; }>; -export type BookingDetailQuery = { event: { id: string | null, title: string, description: string | null, start: string, end: string, room: Array<Room>, phone: string | null, booked_as: string, booked_by: string, created_at: string | null, updated_at: string | null } | null }; +export type BookingDetailQuery = { event: { id: string | null, title: string, description: string | null, start: string, end: string, room: Array<Room>, phone: string, booked_as: string, booked_by: string, created_at: string | null, updated_at: string | null } | null }; export type CreateBookingMutationVariables = Exact<{ event: InputEvent; @@ -75,6 +75,17 @@ export type UpdateBookingMutationVariables = Exact<{ export type UpdateBookingMutation = { editEvent: { en: string, sv: string } | null }; +export type MoveBookingMutationVariables = Exact<{ + id: string; + start: string; + end: string; + previousStart: string; + previousEnd: string; +}>; + + +export type MoveBookingMutation = { moveEvent: { en: string, sv: string } | null }; + export type DeleteBookingMutationVariables = Exact<{ id: string; }>; @@ -99,7 +110,7 @@ export type DeleteRuleMutationVariables = Exact<{ }>; -export type DeleteRuleMutation = { deleteRule: { en: string, sv: string } | null }; +export type DeleteRuleMutation = { deleteRule: boolean | null }; export const BookingFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"Booking"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Event"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"end"}},{"kind":"Field","name":{"kind":"Name","value":"room"}},{"kind":"Field","name":{"kind":"Name","value":"phone"}},{"kind":"Field","name":{"kind":"Name","value":"booked_as"}},{"kind":"Field","name":{"kind":"Name","value":"booked_by"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}}]}}]} as unknown as DocumentNode<BookingFragment, unknown>; export const CurrentUserDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CurrentUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"sub"}},{"kind":"Field","name":{"kind":"Name","value":"cid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"nickname"}},{"kind":"Field","name":{"kind":"Name","value":"locale"}},{"kind":"Field","name":{"kind":"Name","value":"groups"}},{"kind":"Field","name":{"kind":"Name","value":"is_admin"}}]}}]}}]} as unknown as DocumentNode<CurrentUserQuery, CurrentUserQueryVariables>; @@ -107,7 +118,8 @@ export const CalendarDocument = {"kind":"Document","definitions":[{"kind":"Opera export const BookingDetailDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"BookingDetail"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"event"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"Booking"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"Booking"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Event"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"end"}},{"kind":"Field","name":{"kind":"Name","value":"room"}},{"kind":"Field","name":{"kind":"Name","value":"phone"}},{"kind":"Field","name":{"kind":"Name","value":"booked_as"}},{"kind":"Field","name":{"kind":"Name","value":"booked_by"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}}]}}]} as unknown as DocumentNode<BookingDetailQuery, BookingDetailQueryVariables>; export const CreateBookingDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateBooking"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"event"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"InputEvent"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createEvent"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"event"},"value":{"kind":"Variable","name":{"kind":"Name","value":"event"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"en"}},{"kind":"Field","name":{"kind":"Name","value":"sv"}}]}}]}}]} as unknown as DocumentNode<CreateBookingMutation, CreateBookingMutationVariables>; export const UpdateBookingDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateBooking"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"event"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"InputEvent"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"editEvent"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"event"},"value":{"kind":"Variable","name":{"kind":"Name","value":"event"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"en"}},{"kind":"Field","name":{"kind":"Name","value":"sv"}}]}}]}}]} as unknown as DocumentNode<UpdateBookingMutation, UpdateBookingMutationVariables>; +export const MoveBookingDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"MoveBooking"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"start"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"end"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"previousStart"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"previousEnd"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"moveEvent"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}},{"kind":"Argument","name":{"kind":"Name","value":"start"},"value":{"kind":"Variable","name":{"kind":"Name","value":"start"}}},{"kind":"Argument","name":{"kind":"Name","value":"end"},"value":{"kind":"Variable","name":{"kind":"Name","value":"end"}}},{"kind":"Argument","name":{"kind":"Name","value":"previousStart"},"value":{"kind":"Variable","name":{"kind":"Name","value":"previousStart"}}},{"kind":"Argument","name":{"kind":"Name","value":"previousEnd"},"value":{"kind":"Variable","name":{"kind":"Name","value":"previousEnd"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"en"}},{"kind":"Field","name":{"kind":"Name","value":"sv"}}]}}]}}]} as unknown as DocumentNode<MoveBookingMutation, MoveBookingMutationVariables>; export const DeleteBookingDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteBooking"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteEvent"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"en"}},{"kind":"Field","name":{"kind":"Name","value":"sv"}}]}}]}}]} as unknown as DocumentNode<DeleteBookingMutation, DeleteBookingMutationVariables>; export const RulesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Rules"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"rules"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"updated_at"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"day_mask"}},{"kind":"Field","name":{"kind":"Name","value":"start_date"}},{"kind":"Field","name":{"kind":"Name","value":"end_date"}},{"kind":"Field","name":{"kind":"Name","value":"start_time"}},{"kind":"Field","name":{"kind":"Name","value":"end_time"}},{"kind":"Field","name":{"kind":"Name","value":"priority"}},{"kind":"Field","name":{"kind":"Name","value":"allow"}},{"kind":"Field","name":{"kind":"Name","value":"room"}}]}}]}}]} as unknown as DocumentNode<RulesQuery, RulesQueryVariables>; export const CreateRuleDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateRule"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"rule"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"InputRule"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createRule"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"rule"},"value":{"kind":"Variable","name":{"kind":"Name","value":"rule"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"en"}},{"kind":"Field","name":{"kind":"Name","value":"sv"}}]}}]}}]} as unknown as DocumentNode<CreateRuleMutation, CreateRuleMutationVariables>; -export const DeleteRuleDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteRule"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteRule"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"en"}},{"kind":"Field","name":{"kind":"Name","value":"sv"}}]}}]}}]} as unknown as DocumentNode<DeleteRuleMutation, DeleteRuleMutationVariables>; +export const DeleteRuleDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteRule"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteRule"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}]}}]} as unknown as DocumentNode<DeleteRuleMutation, DeleteRuleMutationVariables>; diff --git a/frontend/src/lib/dates.test.ts b/frontend/src/lib/dates.test.ts index 5cec8c5..eb60bf0 100644 --- a/frontend/src/lib/dates.test.ts +++ b/frontend/src/lib/dates.test.ts @@ -1,7 +1,24 @@ import { describe, expect, it } from "vite-plus/test"; -import { localInput, parseDate } from "./dates"; +import { bookingSearchDate, localInput, parseDate } from "./dates"; +import { parseDateTime } from "@internationalized/date"; describe("booking dates", () => { + it("normalizes UTC and offset query parameters into picker-compatible local values", () => { + for (const value of ["2026-09-10T12:00:00Z", "2026-09-10T14:00:00+02:00", "2026-09-10T12:00"]) { + const normalized = bookingSearchDate(value); + + expect(normalized).toBe(localInput(new Date(value))); + expect(() => parseDateTime(normalized!)).not.toThrow(); + expect(new Date(normalized!).getTime()).toBe(new Date(value).getTime()); + } + }); + + it("discards missing, invalid, and out-of-picker-range query parameters", () => { + for (const value of [undefined, 123, "not-a-date", "+010000-01-01T00:00:00Z"]) { + expect(bookingSearchDate(value)).toBeUndefined(); + } + }); + it("accepts ISO dates and the legacy milliseconds contract", () => { const date = new Date("2026-10-25T01:30:00.000Z"); diff --git a/frontend/src/lib/dates.ts b/frontend/src/lib/dates.ts index 0b4648d..b89cf13 100644 --- a/frontend/src/lib/dates.ts +++ b/frontend/src/lib/dates.ts @@ -13,3 +13,15 @@ export function parseDate(value: string): Date { export function localInput(value: Date) { return format(value, "yyyy-MM-dd'T'HH:mm"); } + +export function bookingSearchDate(value: unknown): string | undefined { + if (typeof value !== "string") { + return undefined; + } + + const date = new Date(/^\d{4}-\d{2}-\d{2}$/.test(value) ? `${value}T00:00` : value); + + return isValid(date) && date.getFullYear() >= 1 && date.getFullYear() <= 9999 + ? localInput(date) + : undefined; +} diff --git a/frontend/src/lib/rules.ts b/frontend/src/lib/rules.ts new file mode 100644 index 0000000..9e647b3 --- /dev/null +++ b/frontend/src/lib/rules.ts @@ -0,0 +1,17 @@ +import { format } from "date-fns"; +import type { RulesQuery } from "@/generated/graphql"; +import { parseDate } from "./dates"; + +export type RuleDetails = NonNullable<NonNullable<RulesQuery["rules"]>[number]>; + +export const ruleWeekdays = { + en: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], + sv: ["Mån", "Tis", "Ons", "Tor", "Fre", "Lör", "Sön"], +}; + +export const ruleDateText = (value: string | null | undefined, withTime = false) => + value ? format(parseDate(value), withTime ? "d MMM yyyy, HH:mm" : "d MMM yyyy") : "—"; + +export const ruleDayText = (mask: number | null | undefined, language: "en" | "sv") => + ruleWeekdays[language].filter((_, index) => ((mask ?? 0) & (1 << index)) !== 0).join(" · ") || + "—"; diff --git a/frontend/src/pages/calendar.tsx b/frontend/src/pages/calendar.tsx index c590647..f849ed3 100644 --- a/frontend/src/pages/calendar.tsx +++ b/frontend/src/pages/calendar.tsx @@ -11,7 +11,7 @@ import { toast } from "sonner"; import { request, checkMutation } from "@/api/client"; import { CalendarDocument, - UpdateBookingDocument, + MoveBookingDocument, type BookingFragment, type Room, } from "@/generated/graphql"; @@ -79,20 +79,19 @@ export function CalendarPage() { const move = useMutation({ mutationFn: async ({ booking, start, end }: BookingMove) => { - const { editEvent } = await request(UpdateBookingDocument, { - event: { - id: booking.id, - title: booking.title, - description: booking.description, - start: start.toISOString(), - end: end.toISOString(), - room: booking.room.filter((room): room is Room => Boolean(room)), - booked_as: booking.booked_as, - booking_terms: true, - }, + if (!booking.id) { + throw new Error(t("Booking not found.", "Bokningen hittades inte.")); + } + + const { moveEvent } = await request(MoveBookingDocument, { + id: booking.id, + start: start.toISOString(), + end: end.toISOString(), + previousStart: booking.start, + previousEnd: booking.end, }); - checkMutation(editEvent, language); + checkMutation(moveEvent, language); }, onSuccess: async (_result, { booking }) => { await Promise.all([ diff --git a/frontend/src/pages/rules.tsx b/frontend/src/pages/rules.tsx index 90fbbad..21d9419 100644 --- a/frontend/src/pages/rules.tsx +++ b/frontend/src/pages/rules.tsx @@ -1,27 +1,13 @@ -import { formText } from "@/lib/forms"; -import { useState, type FormEvent } from "react"; +import { useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { format } from "date-fns"; import { Plus, Trash2, ArrowUpDown } from "lucide-react"; import { toast } from "sonner"; -import { request, checkMutation } from "@/api/client"; -import { - RulesDocument, - CreateRuleDocument, - DeleteRuleDocument, - type Room, - type RulesQuery, -} from "@/generated/graphql"; -import { rooms, roomName } from "@/lib/rooms"; -import { parseDate } from "@/lib/dates"; +import { request } from "@/api/client"; +import { RulesDocument, DeleteRuleDocument, type Room } from "@/generated/graphql"; +import { roomName } from "@/lib/rooms"; import { useUser } from "@/lib/user"; import { useLanguage } from "@/lib/language"; import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Textarea } from "@/components/ui/textarea"; -import { Checkbox } from "@/components/ui/checkbox"; -import { DatePicker, TimePicker } from "@/components/date-time-fields"; import { Badge } from "@/components/ui/badge"; import { Card } from "@/components/ui/card"; import { @@ -42,7 +28,9 @@ import { } from "@/components/ui/dialog"; import { Failure, Loading } from "@/components/feedback"; -type RuleDetails = NonNullable<NonNullable<RulesQuery["rules"]>[number]>; +import { RuleForm } from "@/components/rule-form"; +import { RuleDetails as RuleDetailsContent } from "@/components/rule-details"; +import { ruleDateText as dateText, ruleDayText, type RuleDetails } from "@/lib/rules"; type SortKey = | "title" @@ -76,18 +64,10 @@ export function RulesPage() { const cache = useQueryClient(); const [creating, setCreating] = useState(false); const [deleting, setDeleting] = useState<{ id: string; title: string } | null>(null); - const [selected, setSelected] = useState<Room[]>([]); - const [days, setDays] = useState(0); const [detailsId, setDetailsId] = useState<string | null>(null); const [sortKey, setSortKey] = useState<SortKey>("title"); const [ascending, setAscending] = useState(true); const [pageIndex, setPageIndex] = useState(0); - const [error, setError] = useState(""); - - const weekdays = - language === "en" - ? ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] - : ["Mån", "Tis", "Ons", "Tor", "Fre", "Lör", "Sön"]; const query = useQuery({ queryKey: ["rules"], queryFn: () => request(RulesDocument, {}) }); @@ -121,62 +101,20 @@ export function RulesPage() { { key: "allow", label: t("Availability", "Tillgänglighet") }, ]; - const dateText = (value: string | null | undefined, withTime = false) => - value ? format(parseDate(value), withTime ? "d MMM yyyy, HH:mm" : "d MMM yyyy") : "—"; - - const dayText = (mask: number | null | undefined) => - weekdays.filter((_, index) => ((mask ?? 0) & (1 << index)) !== 0).join(" · ") || "—"; + const dayText = (mask: number | null | undefined) => ruleDayText(mask, language); async function invalidate() { await cache.invalidateQueries({ queryKey: ["rules"] }); await cache.invalidateQueries({ queryKey: ["calendar"] }); } - const create = useMutation({ - mutationFn: async (form: FormData) => { - if (!selected.length || !days) { - throw new Error( - t("Select rooms and at least one weekday.", "Välj rum och minst en veckodag."), - ); - } - - const rule = { - title: formText(form, "title").trim(), - description: formText(form, "description"), - priority: Number(form.get("priority")), - allow: form.get("allow") === "true", - day_mask: days, - room: selected, - start_date: formText(form, "start_date"), - end_date: formText(form, "end_date"), - start_time: formText(form, "start_time").slice(0, 5), - end_time: formText(form, "end_time").slice(0, 5), - }; - - if (rule.end_date < rule.start_date || rule.end_time <= rule.start_time) { - throw new Error( - t("The rule must end after it starts.", "Regeln måste sluta efter att den börjar."), - ); - } - - const result = await request(CreateRuleDocument, { rule }); - - checkMutation(result.createRule, language); - }, - onSuccess: async () => { - await invalidate(); - setCreating(false); - setSelected([]); - toast.success(t("Rule created", "Regeln skapades")); - }, - onError: (reason) => setError(reason.message), - }); - const remove = useMutation({ mutationFn: async (id: string) => { const result = await request(DeleteRuleDocument, { id }); - checkMutation(result.deleteRule, language); + if (!result.deleteRule) { + throw new Error(t("Could not delete rule", "Kunde inte ta bort regel")); + } }, onSuccess: async () => { await invalidate(); @@ -186,25 +124,12 @@ export function RulesPage() { onError: (reason) => toast.error(reason.message), }); - function submit(event: FormEvent<HTMLFormElement>) { - event.preventDefault(); - setError(""); - create.mutate(new FormData(event.currentTarget)); - } - return ( <> <div className="flex justify-between items-end flex-wrap gap-5 mb-7"> <h1 className="page-title">{t("Rules", "Regler")}</h1> {user?.is_admin && ( - <Button - onClick={() => { - setError(""); - setDays(0); - setSelected([]); - setCreating(true); - }} - > + <Button onClick={() => setCreating(true)}> <Plus className="size-4" /> {t("New rule", "Ny regel")} </Button> @@ -339,37 +264,7 @@ export function RulesPage() { <DialogTitle>{t("Rule details", "Regeldetaljer")}</DialogTitle> <DialogDescription>{details?.title}</DialogDescription> </DialogHeader> - {details && ( - <dl className="grid gap-4 sm:grid-cols-2 text-sm"> - {[ - [t("Title", "Titel"), details.title], - [t("Priority", "Prioritet"), details.priority], - [t("Start date", "Startdatum"), dateText(details.start_date)], - [t("End date", "Slutdatum"), dateText(details.end_date)], - [t("Start time", "Starttid"), details.start_time], - [t("End time", "Sluttid"), details.end_time], - [t("Description", "Beskrivning"), details.description], - [t("Rooms", "Rum"), details.room?.map(roomName).join(", ")], - [t("Weekdays", "Veckodagar"), dayText(details.day_mask)], - [ - t("Availability", "Tillgänglighet"), - details.allow ? t("Allowed", "Tillåten") : t("Blocked", "Spärrad"), - ], - [t("Created", "Skapad"), dateText(details.created_at, true)], - [t("Updated", "Uppdaterad"), dateText(details.updated_at, true)], - ].map(([label, value]) => ( - <div key={label}> - <dt className="text-muted-foreground">{label}</dt> - <dd className="mt-1 whitespace-pre-wrap">{value ?? "—"}</dd> - </div> - ))} - </dl> - )} - <DialogFooter> - <Button variant="outline" onClick={() => setDetailsId(null)}> - {t("Close", "Stäng")} - </Button> - </DialogFooter> + {details && <RuleDetailsContent details={details} onClose={() => setDetailsId(null)} />} </DialogContent> </Dialog> <Dialog open={creating} onOpenChange={setCreating}> @@ -378,96 +273,13 @@ export function RulesPage() { <DialogTitle>{t("New rule", "Ny regel")}</DialogTitle> <DialogDescription className="sr-only">{t("New rule", "Ny regel")}</DialogDescription> </DialogHeader> - <form onSubmit={submit} className="space-y-5"> - <div className="field"> - <Label htmlFor="rule-title">{t("Title", "Titel")}</Label> - <Input id="rule-title" name="title" required maxLength={200} /> - </div> - <div className="grid grid-cols-2 gap-4"> - <div className="field"> - <Label htmlFor="priority">{t("Priority", "Prioritet")}</Label> - <Input id="priority" name="priority" type="number" required defaultValue={10} /> - </div> - <div className="field"> - <Label htmlFor="allow">{t("Availability", "Tillgänglighet")}</Label> - <select id="allow" name="allow" className="native-select"> - <option value="true">{t("Allowed", "Tillåten")}</option> - <option value="false">{t("Blocked", "Spärrad")}</option> - </select> - </div> - <DatePicker - label={t("Start date", "Startdatum")} - name="start_date" - defaultValue={format(new Date(), "yyyy-MM-dd")} - /> - <DatePicker - label={t("End date", "Slutdatum")} - name="end_date" - defaultValue="2040-12-31" - /> - <TimePicker - label={t("Start time", "Starttid")} - name="start_time" - defaultValue="08:00" - /> - <TimePicker label={t("End time", "Sluttid")} name="end_time" defaultValue="17:00" /> - </div> - <fieldset> - <legend className="text-sm font-medium mb-3">{t("Weekdays", "Veckodagar")}</legend> - <div className="flex flex-wrap gap-3"> - {weekdays.map((day, index) => ( - <label className="flex gap-2 items-center text-sm" key={day}> - <Checkbox - aria-label={day} - checked={Boolean(days & (1 << index))} - onCheckedChange={(checked) => - setDays(checked ? days | (1 << index) : days & ~(1 << index)) - } - /> - {day} - </label> - ))} - </div> - </fieldset> - <fieldset> - <legend className="text-sm font-medium mb-3">{t("Rooms", "Rum")}</legend> - <div className="grid grid-cols-2 gap-3"> - {rooms.map((room) => ( - <label className="flex gap-2 items-center text-sm" key={room.id}> - <Checkbox - aria-label={room.name} - checked={selected.includes(room.id)} - onCheckedChange={(checked) => - setSelected( - checked - ? [...selected, room.id] - : selected.filter((id) => id !== room.id), - ) - } - /> - {room.name} - </label> - ))} - </div> - </fieldset> - <div className="field"> - <Label htmlFor="rule-description">{t("Description", "Beskrivning")}</Label> - <Textarea id="rule-description" name="description" /> - </div> - {error && ( - <p role="alert" className="text-sm text-destructive"> - {error} - </p> - )} - <DialogFooter> - <Button type="button" variant="outline" onClick={() => setCreating(false)}> - {t("Cancel", "Avbryt")} - </Button> - <Button type="submit" disabled={create.isPending}> - {t("Save rule", "Spara regel")} - </Button> - </DialogFooter> - </form> + <RuleForm + onCancel={() => setCreating(false)} + onSaved={async () => { + await invalidate(); + setCreating(false); + }} + /> </DialogContent> </Dialog> <Dialog diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx index 064259d..76de838 100644 --- a/frontend/src/router.tsx +++ b/frontend/src/router.tsx @@ -13,6 +13,7 @@ import { Button } from "@/components/ui/button"; import { useUser } from "@/lib/user"; import { useLanguage } from "@/lib/language"; import { Failure, Loading } from "@/components/feedback"; +import { bookingSearchDate } from "@/lib/dates"; function Layout() { const user = useUser(); @@ -117,14 +118,8 @@ const newRoute = createRoute({ getParentRoute: () => rootRoute, path: "/new-event", validateSearch: (search: Record<string, unknown>): { start?: string; end?: string } => ({ - start: - typeof search.start === "string" && !Number.isNaN(Date.parse(search.start)) - ? search.start - : undefined, - end: - typeof search.end === "string" && !Number.isNaN(Date.parse(search.end)) - ? search.end - : undefined, + start: bookingSearchDate(search.start), + end: bookingSearchDate(search.end), }), component: lazyRouteComponent(() => import("@/pages/booking"), "NewBookingPage"), }); From 05c562467e25d48c0806c735f507812b10c01da1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 17:23:47 +0200 Subject: [PATCH 34/41] Unify UI utilities, theme tokens, and native selects --- frontend/package.json | 2 - frontend/src/components/booking-form.tsx | 6 +- frontend/src/components/rule-form.tsx | 5 +- frontend/src/components/ui/badge.tsx | 2 +- frontend/src/components/ui/button.tsx | 2 +- frontend/src/components/ui/card.tsx | 2 +- frontend/src/components/ui/checkbox.tsx | 2 +- frontend/src/components/ui/dialog.tsx | 2 +- frontend/src/components/ui/input.tsx | 2 +- frontend/src/components/ui/label.tsx | 2 +- frontend/src/components/ui/native-select.tsx | 19 ++++++ frontend/src/components/ui/table.tsx | 2 +- frontend/src/components/ui/tabs.tsx | 2 +- frontend/src/components/ui/textarea.tsx | 2 +- frontend/src/lib/utils.ts | 7 +-- frontend/src/router.tsx | 4 +- frontend/src/styles.css | 65 +++++++++++--------- pnpm-lock.yaml | 11 ---- 18 files changed, 74 insertions(+), 65 deletions(-) create mode 100644 frontend/src/components/ui/native-select.tsx diff --git a/frontend/package.json b/frontend/package.json index 61f9095..253e270 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,7 +15,6 @@ "@tanstack/react-query": "^5.102.8", "@tanstack/react-router": "^1.170.32", "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", "cn": "^0.2.5", "date-fns": "^4.4.0", "graphql": "^16.12.0", @@ -25,7 +24,6 @@ "react-aria-components": "1.21.1", "react-dom": "^19.2.8", "sonner": "^2.0.8", - "tailwind-merge": "^3.6.0", "temporal-polyfill": "^1.0.4" }, "devDependencies": { diff --git a/frontend/src/components/booking-form.tsx b/frontend/src/components/booking-form.tsx index 33bb2a1..0a7f139 100644 --- a/frontend/src/components/booking-form.tsx +++ b/frontend/src/components/booking-form.tsx @@ -17,6 +17,7 @@ import { useUser } from "@/lib/user"; import { useLanguage } from "@/lib/language"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; +import { NativeSelect } from "@/components/ui/native-select"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Checkbox } from "@/components/ui/checkbox"; @@ -181,10 +182,9 @@ export function BookingForm({ </div> <div className="field"> <Label htmlFor="booked_as">{t("Booking as", "Bokas som")}</Label> - <select + <NativeSelect id="booked_as" name="booked_as" - className="native-select" defaultValue={booking?.booked_as ?? ""} required > @@ -196,7 +196,7 @@ export function BookingForm({ {group} </option> ))} - </select> + </NativeSelect> </div> </div> <fieldset> diff --git a/frontend/src/components/rule-form.tsx b/frontend/src/components/rule-form.tsx index 0f69145..02a1e09 100644 --- a/frontend/src/components/rule-form.tsx +++ b/frontend/src/components/rule-form.tsx @@ -10,6 +10,7 @@ import { useLanguage } from "@/lib/language"; import { ruleWeekdays } from "@/lib/rules"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; +import { NativeSelect } from "@/components/ui/native-select"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Checkbox } from "@/components/ui/checkbox"; @@ -86,10 +87,10 @@ export function RuleForm({ </div> <div className="field"> <Label htmlFor="allow">{t("Availability", "Tillgänglighet")}</Label> - <select id="allow" name="allow" className="native-select"> + <NativeSelect id="allow" name="allow"> <option value="true">{t("Allowed", "Tillåten")}</option> <option value="false">{t("Blocked", "Spärrad")}</option> - </select> + </NativeSelect> </div> <DatePicker label={t("Start date", "Startdatum")} diff --git a/frontend/src/components/ui/badge.tsx b/frontend/src/components/ui/badge.tsx index 681594e..196da20 100644 --- a/frontend/src/components/ui/badge.tsx +++ b/frontend/src/components/ui/badge.tsx @@ -1,6 +1,6 @@ import * as React from "react"; import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "cn"; +import { cn } from "@/lib/utils"; import { Slot } from "radix-ui"; const badgeVariants = cva( diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx index 73bce6f..db10fe3 100644 --- a/frontend/src/components/ui/button.tsx +++ b/frontend/src/components/ui/button.tsx @@ -1,6 +1,6 @@ import * as React from "react"; import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "cn"; +import { cn } from "@/lib/utils"; import { Slot } from "radix-ui"; const buttonVariants = cva( diff --git a/frontend/src/components/ui/card.tsx b/frontend/src/components/ui/card.tsx index 01b6263..750acfc 100644 --- a/frontend/src/components/ui/card.tsx +++ b/frontend/src/components/ui/card.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { cn } from "cn"; +import { cn } from "@/lib/utils"; function Card({ className, ...props }: React.ComponentProps<"div">) { return ( diff --git a/frontend/src/components/ui/checkbox.tsx b/frontend/src/components/ui/checkbox.tsx index bc1b730..0de36b1 100644 --- a/frontend/src/components/ui/checkbox.tsx +++ b/frontend/src/components/ui/checkbox.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { cn } from "cn"; +import { cn } from "@/lib/utils"; import { CheckIcon } from "lucide-react"; import { Checkbox as CheckboxPrimitive } from "radix-ui"; diff --git a/frontend/src/components/ui/dialog.tsx b/frontend/src/components/ui/dialog.tsx index d631cd0..cd3312f 100644 --- a/frontend/src/components/ui/dialog.tsx +++ b/frontend/src/components/ui/dialog.tsx @@ -1,7 +1,7 @@ "use client"; import * as React from "react"; -import { cn } from "cn"; +import { cn } from "@/lib/utils"; import { XIcon } from "lucide-react"; import { Dialog as DialogPrimitive } from "radix-ui"; diff --git a/frontend/src/components/ui/input.tsx b/frontend/src/components/ui/input.tsx index 86342c1..494f17d 100644 --- a/frontend/src/components/ui/input.tsx +++ b/frontend/src/components/ui/input.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { cn } from "cn"; +import { cn } from "@/lib/utils"; function Input({ className, type, ...props }: React.ComponentProps<"input">) { return ( diff --git a/frontend/src/components/ui/label.tsx b/frontend/src/components/ui/label.tsx index fbe68a5..c3e123b 100644 --- a/frontend/src/components/ui/label.tsx +++ b/frontend/src/components/ui/label.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { cn } from "cn"; +import { cn } from "@/lib/utils"; import { Label as LabelPrimitive } from "radix-ui"; function Label({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) { diff --git a/frontend/src/components/ui/native-select.tsx b/frontend/src/components/ui/native-select.tsx new file mode 100644 index 0000000..147fd9f --- /dev/null +++ b/frontend/src/components/ui/native-select.tsx @@ -0,0 +1,19 @@ +import type { ComponentProps } from "react"; +import { cn } from "@/lib/utils"; + +function NativeSelect({ className, ...props }: ComponentProps<"select">) { + return ( + <select + data-slot="native-select" + className={cn( + "min-h-10 w-full rounded-md border border-input bg-card px-3 py-2 text-sm disabled:cursor-not-allowed disabled:opacity-50", + "focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50", + "aria-invalid:border-destructive aria-invalid:ring-destructive/20", + className, + )} + {...props} + /> + ); +} + +export { NativeSelect }; diff --git a/frontend/src/components/ui/table.tsx b/frontend/src/components/ui/table.tsx index c7c83dd..2a1092b 100644 --- a/frontend/src/components/ui/table.tsx +++ b/frontend/src/components/ui/table.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { cn } from "cn"; +import { cn } from "@/lib/utils"; function Table({ className, ...props }: React.ComponentProps<"table">) { return ( diff --git a/frontend/src/components/ui/tabs.tsx b/frontend/src/components/ui/tabs.tsx index 625b2cd..915872f 100644 --- a/frontend/src/components/ui/tabs.tsx +++ b/frontend/src/components/ui/tabs.tsx @@ -1,6 +1,6 @@ import * as React from "react"; import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "cn"; +import { cn } from "@/lib/utils"; import { Tabs as TabsPrimitive } from "radix-ui"; function Tabs({ diff --git a/frontend/src/components/ui/textarea.tsx b/frontend/src/components/ui/textarea.tsx index 741a012..42723fb 100644 --- a/frontend/src/components/ui/textarea.tsx +++ b/frontend/src/components/ui/textarea.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { cn } from "cn"; +import { cn } from "@/lib/utils"; function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { return ( diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts index a5ef193..fe0d169 100644 --- a/frontend/src/lib/utils.ts +++ b/frontend/src/lib/utils.ts @@ -1,6 +1 @@ -import { clsx, type ClassValue } from "clsx"; -import { twMerge } from "tailwind-merge"; - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} +export { cn } from "cn"; diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx index 76de838..99b64af 100644 --- a/frontend/src/router.tsx +++ b/frontend/src/router.tsx @@ -37,11 +37,11 @@ function Layout() { <div className="max-w-[1560px] mx-auto px-4 sm:px-12 flex items-center justify-between min-h-20 gap-4"> <div className="flex items-center gap-2 sm:gap-12"> <Link to="/" aria-label="BookIT home" className="flex items-center gap-2.5"> - <span className="size-9 rounded-lg bg-[#2196f3] text-white grid place-items-center"> + <span className="size-9 rounded-lg bg-brand text-white grid place-items-center"> <CalendarDays className="size-5" /> </span> <span className="hidden sm:inline text-2xl tracking-tight font-bold"> - book<span className="text-[#2196f3]">IT</span> + book<span className="text-brand">IT</span> </span> </Link> <nav aria-label={t("Main navigation", "Huvudnavigation")} className="flex gap-1"> diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 8ee50d5..1738e72 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -5,35 +5,52 @@ --color-background: var(--background); --color-foreground: var(--foreground); --color-card: var(--card); - --color-card-foreground: var(--foreground); - --color-popover: var(--card); - --color-popover-foreground: var(--foreground); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); --color-primary: var(--primary); - --color-primary-foreground: #fff; - --color-secondary: #e8f3fb; - --color-secondary-foreground: #1769aa; - --color-muted: #f1f5f8; - --color-muted-foreground: #617083; - --color-accent: #e8f3fb; - --color-accent-foreground: #1769aa; - --color-destructive: #b42339; - --color-border: #dfe6ed; - --color-input: #d1dae4; - --color-ring: #2196f3; + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-brand: var(--brand); --color-chart-1: var(--chart-1); --color-chart-3: var(--chart-3); - --radius-sm: 0.375rem; - --radius-md: 0.5rem; - --radius-lg: 0.75rem; - --radius-xl: 1rem; + --radius-sm: calc(var(--radius) - 2px); + --radius-md: var(--radius); + --radius-lg: calc(var(--radius) + 4px); + --radius-xl: calc(var(--radius) + 8px); } :root { --background: #f6f8fb; --foreground: #17283a; --card: #fff; + --card-foreground: #17283a; + --popover: #fff; + --popover-foreground: #17283a; --primary: #1769aa; --primary-foreground: #fff; + --secondary: #e8f3fb; + --secondary-foreground: #1769aa; + --muted: #f1f5f8; + --muted-foreground: #617083; + --accent: #e8f3fb; + --accent-foreground: #1769aa; + --destructive: #b42339; + --border: #dfe6ed; + --input: #d1dae4; + --ring: #2196f3; + --brand: #2196f3; + --eyebrow: #697a8d; + --radius: 0.5rem; --chart-1: #2196f3; --chart-3: #ff9100; font-family: "Inter", "Segoe UI", Arial, sans-serif; @@ -84,7 +101,7 @@ textarea { font-weight: 700; letter-spacing: 0.16em; text-transform: uppercase; - color: #697a8d; + color: var(--eyebrow); } .page-title { @@ -99,16 +116,6 @@ textarea { gap: 0.55rem; } -.native-select { - min-height: 2.5rem; - width: 100%; - border: 1px solid var(--color-input); - border-radius: 0.5rem; - padding: 0.5rem 0.75rem; - font-size: 0.875rem; - background: white; -} - .booking-multi-room { background-image: var(--booking-stripes); color: white; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ef87554..b7803db 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -252,9 +252,6 @@ importers: class-variance-authority: specifier: ^0.7.1 version: 0.7.1 - clsx: - specifier: ^2.1.1 - version: 2.1.1 cn: specifier: ^0.2.5 version: 0.2.5 @@ -282,9 +279,6 @@ importers: sonner: specifier: ^2.0.8 version: 2.0.8(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - tailwind-merge: - specifier: ^3.6.0 - version: 3.6.0 temporal-polyfill: specifier: ^1.0.4 version: 1.0.4 @@ -4681,9 +4675,6 @@ packages: swap-case@3.0.3: resolution: {integrity: sha512-6p4op8wE9CQv7uDFzulI6YXUw4lD9n4oQierdbFThEKVWVQcbQcUjdP27W8XE7V4QnWmnq9jueSHceyyQnqQVA==} - tailwind-merge@3.6.0: - resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==} - tailwindcss@4.3.3: resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==} @@ -9361,8 +9352,6 @@ snapshots: swap-case@3.0.3: {} - tailwind-merge@3.6.0: {} - tailwindcss@4.3.3: {} tapable@2.3.3: {} From 99fa0f9bfbe9e9a10c857c0910d9118c4f4b2c66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Theodor=20Angerg=C3=A5rd?= <portalprogramming@gmail.com> Date: Sun, 6 Sep 2026 17:58:24 +0200 Subject: [PATCH 35/41] Integrate automatic cleanup, simplify tooling, and refresh theme --- README.md | 56 ++--- bookit/src/cleanup.ts | 45 ++++ bookit/src/index.ts | 23 ++- bookit/src/tests/event.test.ts | 193 ------------------ bookit/src/tests/fixtures/main-schema.graphql | 124 ----------- bookit/src/tests/schema-compatibility.test.ts | 100 --------- bookit/src/tests/session-store.test.ts | 65 ------ bookit/startup.sh | 2 +- compose.cleanup.yml | 7 - db-scripts/Dockerfile | 11 - db-scripts/cleanup.sh | 26 --- db-scripts/delete-data.sql | 4 - docker-compose.yml | 13 -- e2e/README.md | 60 ------ e2e/cleanup.spec.ts | 101 ++++----- e2e/compose.ts | 33 +-- frontend/index.html | 2 +- frontend/package.json | 1 + frontend/src/components/booking-form.tsx | 8 +- frontend/src/components/ui/badge.tsx | 2 +- frontend/src/components/ui/button.tsx | 2 +- frontend/src/lib/rooms.ts | 6 +- frontend/src/pages/booking.tsx | 4 +- frontend/src/router.tsx | 10 +- frontend/src/styles.css | 36 ++-- gamma/bookit-client.sql | 10 + package.json | 1 + pnpm-lock.yaml | 156 ++++++++++---- pnpm-workspace.yaml | 30 ++- 29 files changed, 339 insertions(+), 792 deletions(-) create mode 100644 bookit/src/cleanup.ts delete mode 100644 bookit/src/tests/event.test.ts delete mode 100644 bookit/src/tests/fixtures/main-schema.graphql delete mode 100644 bookit/src/tests/schema-compatibility.test.ts delete mode 100644 bookit/src/tests/session-store.test.ts delete mode 100644 compose.cleanup.yml delete mode 100644 db-scripts/Dockerfile delete mode 100644 db-scripts/cleanup.sh delete mode 100644 db-scripts/delete-data.sql delete mode 100644 e2e/README.md diff --git a/README.md b/README.md index b0e68f5..024b821 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,6 @@ BookIT, after confirming `DATABASE_URL` points to your local database: ```sh pnpm --dir bookit migrate -docker compose up -d db-scripts pnpm --dir bookit dev ``` @@ -54,36 +53,38 @@ Sign in with `bookmember` (digIT), `bookadmin` (BookIT admin), or `bookguest` The GraphQL endpoint is `/api/graphql/v1`. Ordinary startup preserves database contents; do not use `docker compose down -v` -to stop development. Use `docker compose stop` instead. Compose is development-only; -deployments must provide their own authentication settings with `NODE_ENV=production`. -Existing deployments can keep `SECRET` for OIDC; `SESSION_SECRET` is used if `SECRET` is absent. -The `ghcr.io/cthit/bookit` image serves both the API and built frontend on port 8080. +to stop development. Use `docker compose stop` instead. Compose is development-only. Vite is only used as a separate server during development for live updates. +## Deployment + +> [!IMPORTANT] +> `bookit-node-frontend` and `bookit-node-backend` have been merged into **`bookit-node`**. +> Replace the two application containers with one using `ghcr.io/cthit/bookit:<tag>`. +> It serves both the frontend and API on port **8080**; route `/` and `/api/*` to +> this container and remove the separate frontend deployment. +> +> Move the backend environment settings to the combined container and retain the +> existing PostgreSQL/Redis services and data volumes. Apply the database schema +> explicitly before starting the app; startup no longer applies it automatically. +> Remove any old `db-scripts` cleanup container; the backend now runs cleanup itself. + +Set `NODE_ENV=production` and provide your deployment's authentication settings. +Existing deployments can keep `SECRET` for OIDC; `SESSION_SECRET` is used if `SECRET` is absent. + ## Personal-data cleanup -Run the cleanup service alongside every deployment. It clears phone numbers and -CIDs from bookings that ended at least two weeks ago, runs immediately and then -daily, and retries failures after an hour. The application image does not schedule -this job itself. Development Compose includes `db-scripts`. +The backend automatically clears phone numbers and CIDs from bookings ending on +or before midnight 14 days ago (using the database timezone). It runs at startup +and daily, retries failures after an hour, and logs the number of bookings updated. +No separate cleanup service is needed. -For production, export the application's `DATABASE_URL` and run: +To run it immediately inside the application container: ```sh -docker compose -f compose.cleanup.yml up -d --build -docker compose -f compose.cleanup.yml logs db-scripts +docker exec bookit-node sh ./startup.sh --cleanup ``` -For an immediate one-time verification, run -`docker compose -f compose.cleanup.yml run --rm -e CLEANUP_ONCE=1 db-scripts`. -It exits with a failure status if the cleanup query fails. - -The database must be reachable from that Compose network. If it runs in Docker, -attach the cleanup service to the database's existing network through your -deployment's Compose configuration. Existing cleanup deployments may continue to -use `DB_HOST`, `DB_PORT`, `DB_USER`, `DB_NAME`, and `PGPASSWORD` instead of -`DATABASE_URL`. Check the job's logs as part of deployment verification. - ## API compatibility and limits The public GraphQL schema remains compatible with `main`, including nullable @@ -104,7 +105,16 @@ a maximum of 10,000 rule occurrences per room. Excessive or invalid ranges retur a validation error; existing stored bookings and rules are not deleted or migrated. Run `pnpm check`, `pnpm test` and `pnpm build` for the project checks. -See [browser tests](e2e/README.md) for Playwright setup and CI image testing. + +## E2E tests + +With Docker running and Make installed, run from the repository root: + +```sh +pnpm install --frozen-lockfile +pnpm exec playwright install chromium +pnpm test:e2e +``` ## Technologies diff --git a/bookit/src/cleanup.ts b/bookit/src/cleanup.ts new file mode 100644 index 0000000..f8f3782 --- /dev/null +++ b/bookit/src/cleanup.ts @@ -0,0 +1,45 @@ +import type { PrismaClient } from "@prisma/client"; + +export async function cleanupPersonalData(prisma: PrismaClient): Promise<void> { + // Keep the existing database-local midnight cutoff, including its boundary. + const count = await prisma.$executeRaw` + UPDATE event + SET phone = '', booked_by = '' + WHERE event.end <= NOW()::DATE - 14 + AND (phone <> '' OR booked_by <> '') + `; + + console.log(`BookIT privacy cleanup cleared contact data from ${count} bookings`); +} + +export function startCleanup(prisma: PrismaClient): () => Promise<void> { + let stopped = false; + let timer: NodeJS.Timeout | undefined; + + async function run() { + let delay = 24 * 60 * 60 * 1000; + + try { + await cleanupPersonalData(prisma); + } catch (error) { + console.error("BookIT privacy cleanup failed; retrying in one hour", error); + delay = 60 * 60 * 1000; + } + + // Schedule after completion so a slow query cannot overlap the next run. + if (!stopped) { + timer = setTimeout(() => { + running = run(); + }, delay); + timer.unref(); + } + } + + let running = run(); + + return async () => { + stopped = true; + clearTimeout(timer); + await running; + }; +} diff --git a/bookit/src/index.ts b/bookit/src/index.ts index 4bbad5c..659abc2 100644 --- a/bookit/src/index.ts +++ b/bookit/src/index.ts @@ -8,6 +8,7 @@ import { setupRoutes } from "./routes"; import type { UserInfo } from "./models/user"; import { authRequest } from "./utils"; import { createSessionStore } from "./auth/session-store"; +import { cleanupPersonalData, startCleanup } from "./cleanup"; interface GammaGroup { superGroup?: { type: string; name: string }; @@ -26,6 +27,20 @@ const requiredEnvironment = (name: string): string => { async function main() { process.env.TZ ??= "Europe/Stockholm"; + const prisma = new PrismaClient({ + adapter: new PrismaPg({ connectionString: requiredEnvironment("DATABASE_URL") }), + }); + + if (process.argv.includes("--cleanup")) { + try { + await cleanupPersonalData(prisma); + } finally { + await prisma.$disconnect(); + } + + return; + } + const issuerBaseURL = requiredEnvironment("ISSUER_BASE_URL"); const issuer = new URL(issuerBaseURL); const localIssuer = @@ -62,10 +77,6 @@ async function main() { redis.on("error", (error) => console.error("Redis connection error", error)); - const prisma = new PrismaClient({ - adapter: new PrismaPg({ connectionString: requiredEnvironment("DATABASE_URL") }), - }); - app.get("/api/health", (_req, res) => res.json({ status: "ok" })); app.use( @@ -146,6 +157,7 @@ async function main() { console.log(`BookIT listening on port ${port}`); + const stopCleanup = startCleanup(prisma); let stopping = false; const stop = () => { @@ -155,8 +167,7 @@ async function main() { stopping = true; - void apollo - .stop() + void Promise.all([apollo.stop(), stopCleanup()]) .then(() => Promise.all([redis.close(), prisma.$disconnect()])) .catch((error) => { console.error("Shutdown failed", error); diff --git a/bookit/src/tests/event.test.ts b/bookit/src/tests/event.test.ts deleted file mode 100644 index aaea8ad..0000000 --- a/bookit/src/tests/event.test.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { describe, expect, it, vi } from "vite-plus/test"; -import { Prisma, type PrismaClient, type event, type rule } from "@prisma/client"; -import { - createEvent, - editEvent, - moveEvent, - withBookingTransaction, -} from "../services/event.service"; -import { getResolvers } from "../resolvers"; -import { queryRange } from "../utils/date-range"; -import type { User, Error as BookingError } from "../models"; - -const owner: User = { - sub: "alice", - cid: "alice", - name: "Alice", - nickname: "Alice", - locale: "en", - groups: ["digit"], - is_admin: false, -}; -const member: User = { ...owner, sub: "bob", cid: "bob" }; -const admin: User = { ...member, is_admin: true }; - -function database(overrides: Partial<event> = {}) { - let saved: event = { - id: "00000000-0000-4000-8000-000000000001", - title: "Latest title", - description: "Latest instructions", - phone: "0701111111", - booked_by: owner.cid, - booked_as: "digit", - room: ["GROUP_ROOM"], - start: new Date(Date.now() + 86_400_000), - end: new Date(Date.now() + 90_000_000), - created_at: new Date(), - updated_at: new Date(), - ...overrides, - }; - const transaction = { - event: { - findUnique: vi.fn<() => Promise<event>>(async () => saved), - count: vi.fn<() => Promise<number>>(async () => 0), - update: vi.fn<(input: { data: Partial<event> }) => Promise<event>>( - async ({ data }) => (saved = { ...saved, ...data }), - ), - create: vi.fn<() => Promise<event>>(), - }, - rule: { findMany: vi.fn<() => Promise<rule[]>>(async () => []) }, - }; - const prisma = { - $transaction: vi.fn< - (operation: (tx: typeof transaction) => Promise<unknown>) => Promise<unknown> - >(async (operation) => operation(transaction)), - } as unknown as PrismaClient; - - const input = { - id: saved.id, - title: saved.title, - description: saved.description, - start: saved.start.toISOString(), - end: saved.end.toISOString(), - room: ["GROUP_ROOM"] as const, - booked_by: saved.booked_by, - booked_as: saved.booked_as, - booking_terms: true, - }; - - return { prisma, transaction, saved: () => saved, input: { ...input, room: [...input.room] } }; -} - -describe("booking contact access", () => { - it("keeps a hidden contact when a group member edits other fields", async () => { - const db = database(); - - expect( - await editEvent(db.prisma, { ...db.input, title: "Updated", phone: null }, member), - ).toBeNull(); - expect(db.saved()).toMatchObject({ title: "Updated", phone: "0701111111", booked_by: "alice" }); - const resolvePhone = getResolvers({ prisma: db.prisma }).Event.phone; - - expect(resolvePhone({ ...db.input, phone: db.saved().phone }, {}, { user: member })).toBe(""); - }); - - it("rejects a forged replacement number from another member", async () => { - const db = database(); - - expect( - (await editEvent(db.prisma, { ...db.input, phone: "0702222222" }, member))?.en, - ).toContain("contact owner"); - expect(db.transaction.event.update).not.toHaveBeenCalled(); - }); - - it.each([owner, admin])( - "allows the contact owner and administrators to replace a number ($cid, admin=$is_admin)", - async (user) => { - const db = database(); - - expect(await editEvent(db.prisma, { ...db.input, phone: "0702222222" }, user)).toBeNull(); - expect(db.saved()).toMatchObject({ phone: "0702222222", booked_by: "alice" }); - }, - ); - - it("requires a fresh contact when editing an anonymized booking", async () => { - const db = database({ booked_by: "", phone: "" }); - - expect(await editEvent(db.prisma, db.input, member)).not.toBeNull(); - expect(await editEvent(db.prisma, { ...db.input, phone: "0702222222" }, member)).toBeNull(); - expect(db.saved()).toMatchObject({ phone: "0702222222", booked_by: "bob" }); - }); -}); - -describe("calendar moves", () => { - it("preserves current metadata and contact details while changing only dates", async () => { - const db = database(); - const before = { ...db.saved() }; - const move = { - id: before.id, - previousStart: before.start.toISOString(), - previousEnd: before.end.toISOString(), - start: new Date(before.start.getTime() + 3_600_000).toISOString(), - end: new Date(before.end.getTime() + 3_600_000).toISOString(), - }; - - expect(await moveEvent(db.prisma, move, member)).toBeNull(); - expect(db.saved()).toEqual({ ...before, start: new Date(move.start), end: new Date(move.end) }); - expect((await moveEvent(db.prisma, move, member))?.en).toContain("time has changed"); - }); - - it("checks group access, overlaps, and recurring rules for moves", async () => { - const db = database(); - const previous = db.saved(); - const move = { - id: previous.id, - previousStart: previous.start.toISOString(), - previousEnd: previous.end.toISOString(), - start: previous.start.toISOString(), - end: previous.end.toISOString(), - }; - - expect((await moveEvent(db.prisma, move, { ...member, groups: [] }))?.en).toContain( - "permission", - ); - db.transaction.event.count.mockResolvedValue(1); - expect((await moveEvent(db.prisma, move, member))?.en).toContain("already taken"); - expect(db.transaction.event.update).not.toHaveBeenCalled(); - db.transaction.event.count.mockResolvedValue(0); - expect(await moveEvent(db.prisma, move, member)).toBeNull(); - expect(db.transaction.rule.findMany).toHaveBeenCalled(); - }); - - it("retries the complete operation after a serialization conflict", async () => { - const db = database(); - const operation = vi - .fn<() => Promise<BookingError | null>>() - .mockRejectedValueOnce( - new Prisma.PrismaClientKnownRequestError("conflict", { - code: "P2034", - clientVersion: "test", - }), - ) - .mockResolvedValue(null); - - expect(await withBookingTransaction(db.prisma, operation)).toBeNull(); - expect(operation).toHaveBeenCalledTimes(2); - }); -}); - -describe("date-range limits", () => { - it("rejects invalid and excessive queries before database work", () => { - for (const [from, to] of [ - ["invalid", "2026-01-01"], - ["2026-01-02", "2026-01-01"], - ["2026-01-01", "2040-01-01"], - ]) { - expect(() => queryRange(from, to)).toThrow("valid date range"); - } - - expect(() => queryRange("2024-01-01", "2025-01-01")).not.toThrow(); - }); - - it("rejects excessive booking durations before availability queries", async () => { - const db = database(); - const { id: _id, ...input } = db.input; - - expect( - (await createEvent(db.prisma, { ...input, phone: "0701111111", end: "2040-01-01" }, owner)) - ?.en, - ).toContain("366 days"); - expect(db.transaction.event.count).not.toHaveBeenCalled(); - expect(db.transaction.event.create).not.toHaveBeenCalled(); - }); -}); diff --git a/bookit/src/tests/fixtures/main-schema.graphql b/bookit/src/tests/fixtures/main-schema.graphql deleted file mode 100644 index 978b192..0000000 --- a/bookit/src/tests/fixtures/main-schema.graphql +++ /dev/null @@ -1,124 +0,0 @@ -# Public schema from main at 2378a27. Keep as the compatibility baseline. -type Error { - sv: String! - en: String! -} - -type Event { - id: String - start: String! - end: String! - description: String - title: String! - created_at: String - updated_at: String - room: [Room]! - phone: String! - booked_as: String! - booked_by: String! -} - -type Query { - events: [Event] - event(id: String): Event - eventsFT(from: String!, to: String!): [Event] -} - -input InputEvent { - id: String - start: String! - end: String! - description: String - title: String! - created_at: String - updated_at: String - room: [Room]! - phone: String! - booked_as: String! - booking_terms: Boolean! -} - -type Mutation { - createEvent(event: InputEvent): Error - editEvent(event: InputEvent): Error - deleteEvent(id: String): Error -} - -type IllegalSlot { - start: String! - end: String! - description: String - title: String! -} - -type Query { - illegalSlots(from: String!, to: String!): [IllegalSlot!] -} - -enum Room { - BIG_HUB - GROUP_ROOM - CTC - THE_CLOUD -} - -type Rule { - id: String - day_mask: Int - start_date: String - end_date: String - start_time: String - end_time: String - description: String - allow: Boolean - priority: Int - title: String - created_at: String - updated_at: String - room: [Room] -} - -input InputRule { - id: String - day_mask: Int - start_date: String! - end_date: String! - start_time: String! - end_time: String! - description: String! - allow: Boolean - priority: Int - title: String! - created_at: String - updated_at: String - room: [Room]! -} - -type Query { - rules: [Rule] - rule(id: String): Rule -} - -type Mutation { - createRule(rule: InputRule): Error - deleteRule(id: String): Boolean -} - -type User { - sid: String - sub: String - given_name: String - locale: String - picture: String - name: String - nickname: String - family_name: String - jti: String - cid: String - groups: [String] - is_admin: Boolean -} - -type Query { - user: User -} diff --git a/bookit/src/tests/schema-compatibility.test.ts b/bookit/src/tests/schema-compatibility.test.ts deleted file mode 100644 index f7d4992..0000000 --- a/bookit/src/tests/schema-compatibility.test.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { readFileSync, readdirSync } from "node:fs"; -import { resolve } from "node:path"; -import { describe, expect, it, vi } from "vite-plus/test"; -import { buildASTSchema, findBreakingChanges, graphql } from "graphql/index.js"; -import { mergeTypeDefs } from "@graphql-tools/merge"; -import { makeExecutableSchema } from "@graphql-tools/schema"; -import type { PrismaClient } from "@prisma/client"; -import { getResolvers } from "../resolvers"; -import type { User } from "../models"; - -const directory = resolve(__dirname, "../schemas/v1"); -const typeDefs = mergeTypeDefs( - readdirSync(directory) - .filter((file) => file.endsWith(".gql")) - .map((file) => readFileSync(resolve(directory, file), "utf8")), -); -const user: User = { - sub: "member", - cid: "member", - name: "Member", - nickname: "Member", - locale: "en", - groups: ["digit"], - is_admin: false, -}; - -describe("main API compatibility", () => { - it("has no breaking schema changes against the main baseline", () => { - const baseline = readFileSync(resolve(__dirname, "fixtures/main-schema.graphql"), "utf8"); - - expect( - findBreakingChanges(buildASTSchema(mergeTypeDefs(baseline)), buildASTSchema(typeDefs)), - ).toEqual([]); - }); - - it("accepts the legacy Boolean delete mutation and keeps authorization", async () => { - const findUnique = vi.fn<PrismaClient["rule"]["findUnique"]>(); - const schema = makeExecutableSchema({ - typeDefs, - resolvers: getResolvers({ prisma: { rule: { findUnique } } as unknown as PrismaClient }), - }); - const result = await graphql({ - schema, - source: "mutation Delete($id: String) { deleteRule(id: $id) }", - variableValues: { id: "rule" }, - contextValue: { user }, - }); - - expect(result.errors).toBeUndefined(); - expect(result.data?.deleteRule).toBe(false); - expect(findUnique).not.toHaveBeenCalled(); - }); - - it("rejects missing legacy arguments before querying or writing the database", async () => { - // No database methods are supplied: any accidental access fails this test. - const schema = makeExecutableSchema({ - typeDefs, - resolvers: getResolvers({ prisma: {} as PrismaClient }), - }); - - for (const source of [ - "{ event { id } }", - "{ rule { id } }", - "mutation { createEvent { en } }", - "mutation { editEvent { en } }", - "mutation { deleteEvent { en } }", - "mutation { createRule { en } }", - "mutation { deleteRule }", - 'mutation { createEvent(event: {start: "2026-09-07T08:00:00Z", end: "2026-09-07T09:00:00Z", title: "Test", room: [null], booked_as: "digit", booking_terms: true}) { en } }', - ]) { - const result = await graphql({ schema, source, contextValue: { user } }); - - expect(result.errors).toHaveLength(1); - expect(result.errors?.[0]?.extensions.code).toBe("BAD_USER_INPUT"); - } - }); - - it("keeps legacy profile queries valid without exposing session identifiers", async () => { - const schema = makeExecutableSchema({ - typeDefs, - resolvers: getResolvers({ prisma: {} as PrismaClient }), - }); - const result = await graphql({ - schema, - source: "{ user { sid jti given_name family_name picture } }", - contextValue: { - user: { ...user, sid: "private-session", jti: "private-token", given_name: "Given" }, - }, - }); - - expect(result.errors).toBeUndefined(); - expect(result.data?.user).toEqual({ - sid: null, - jti: null, - given_name: "Given", - family_name: null, - picture: null, - }); - }); -}); diff --git a/bookit/src/tests/session-store.test.ts b/bookit/src/tests/session-store.test.ts deleted file mode 100644 index b5a8596..0000000 --- a/bookit/src/tests/session-store.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { describe, expect, it, vi } from "vite-plus/test"; -import { createSessionStore, type SessionRedis } from "../auth/session-store"; - -describe("OIDC session storage", () => { - it("stores a bounded session without access or refresh tokens", async () => { - const client = { - get: vi.fn<SessionRedis["get"]>(), - set: vi.fn<SessionRedis["set"]>().mockResolvedValue("OK"), - del: vi.fn<SessionRedis["del"]>().mockResolvedValue(1), - }; - const store = createSessionStore(client); - const now = Math.floor(Date.now() / 1000); - const payload = { - header: { iat: now, uat: now, exp: now + 120 }, - cookie: { expires: (now + 120) * 1000, maxAge: 120_000 }, - data: { - token_type: "Bearer", - expires_at: String(now + 120), - id_token: "id-token", - access_token: "access-token", - refresh_token: "refresh-token", - groups: ["digit"], - is_admin: false, - sessionExpiresAt: now + 120, - }, - }; - - await new Promise<void>((resolve, reject) => - store.set("session", payload, (error) => (error ? reject(error) : resolve())), - ); - const call = client.set.mock.calls[0]!; - - expect(call[0]).toBe("bookit:oidc:session"); - expect(JSON.parse(call[1]).data).toEqual({ - id_token: "id-token", - groups: ["digit"], - is_admin: false, - sessionExpiresAt: now + 120, - }); - expect(call[2].EX).toBeGreaterThan(0); - expect(call[2].EX).toBeLessThanOrEqual(120); - }); - - it("treats missing and expired sessions as logged out and reports corrupt storage", async () => { - const client = { - get: vi.fn<SessionRedis["get"]>(), - set: vi.fn<SessionRedis["set"]>(), - del: vi.fn<SessionRedis["del"]>(), - }; - const store = createSessionStore(client); - const read = () => - new Promise((resolve, reject) => - store.get("session", (error, value) => (error ? reject(error) : resolve(value))), - ); - - client.get.mockResolvedValue(null); - expect(await read()).toBeNull(); - client.get.mockResolvedValue(JSON.stringify({ header: { exp: 0 }, data: {} })); - expect(await read()).toBeNull(); - client.get.mockResolvedValue("corrupt"); - await expect(read()).rejects.toThrow(SyntaxError); - client.get.mockRejectedValue(new Error("Redis unavailable")); - await expect(read()).rejects.toThrow("Redis unavailable"); - }); -}); diff --git a/bookit/startup.sh b/bookit/startup.sh index 56cc7d2..40d2470 100644 --- a/bookit/startup.sh +++ b/bookit/startup.sh @@ -5,4 +5,4 @@ if [ -z "${DATABASE_URL:-}" ]; then export DATABASE_URL="postgres://${DB_USER:?}:${DB_PASS:?}@${DB_HOST:?}:${DB_PORT:-5432}/${DB_NAME:?}" fi -exec node ./build/index.js +exec node ./build/index.js "$@" diff --git a/compose.cleanup.yml b/compose.cleanup.yml deleted file mode 100644 index 546c076..0000000 --- a/compose.cleanup.yml +++ /dev/null @@ -1,7 +0,0 @@ -# Standalone production cleanup. Use the same database settings as BookIT. -services: - db-scripts: - build: ./db-scripts - restart: unless-stopped - environment: - DATABASE_URL: ${DATABASE_URL:?Set DATABASE_URL to the BookIT database} diff --git a/db-scripts/Dockerfile b/db-scripts/Dockerfile deleted file mode 100644 index 2f34c2d..0000000 --- a/db-scripts/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ - -FROM postgres:12.22-alpine -WORKDIR /cleanup -COPY delete-data.sql ./ -COPY cleanup.sh ./ -USER postgres -ENV DB_USER=postgres -ENV DB_HOST=db -ENV DB_PORT=5432 -ENV DB_NAME=bookit -ENTRYPOINT ["sh", "/cleanup/cleanup.sh"] diff --git a/db-scripts/cleanup.sh b/db-scripts/cleanup.sh deleted file mode 100644 index c51724f..0000000 --- a/db-scripts/cleanup.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/sh -set -eu -trap 'exit 0' INT TERM - -if [ -n "${DATABASE_URL:-}" ]; then - set -- "$DATABASE_URL" -else - set -- -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -fi - -while :; do - if psql -v ON_ERROR_STOP=1 "$@" -f /cleanup/delete-data.sql; then - if [ "${CLEANUP_ONCE:-}" = 1 ]; then - exit 0 - fi - delay=86400 - else - if [ "${CLEANUP_ONCE:-}" = 1 ]; then - exit 1 - fi - echo "BookIT privacy cleanup failed; retrying in one hour" >&2 - delay=3600 - fi - sleep "$delay" & - wait "$!" -done diff --git a/db-scripts/delete-data.sql b/db-scripts/delete-data.sql deleted file mode 100644 index 6cbca75..0000000 --- a/db-scripts/delete-data.sql +++ /dev/null @@ -1,4 +0,0 @@ -UPDATE event - SET phone='', - booked_by='' - WHERE event.end <= NOW()::DATE-14; diff --git a/docker-compose.yml b/docker-compose.yml index 25175f7..ed13881 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,19 +24,6 @@ services: timeout: 5s retries: 20 - db-scripts: - build: ./db-scripts - restart: unless-stopped - depends_on: - db: - condition: service_healthy - environment: - PGPASSWORD: password - DB_USER: postgres - DB_HOST: db - DB_PORT: 5432 - DB_NAME: bookit - gamma-db: image: postgres:16.0-alpine environment: diff --git a/e2e/README.md b/e2e/README.md deleted file mode 100644 index 093708d..0000000 --- a/e2e/README.md +++ /dev/null @@ -1,60 +0,0 @@ -# BookIT browser tests - -Requires Docker, Make, Node and the repository's pnpm dependencies. Install the -browser once with `pnpm exec playwright install chromium`. - -From the repository root: - -```sh -make -C e2e run-e2e # Build and test the current checkout -make -C e2e run-e2e VERSION='<sha>' # Test a published commit image -make -C e2e run-e2e VERSION=v1.2.0 # Test a release or any other published tag -``` - -`pnpm test:e2e` runs the same Make target. For private GHCR images, first use -`docker login ghcr.io` with read-only package access. - -To test a specific image digest: - -```sh -make -C e2e run-e2e \ - BOOKIT_IMAGE='ghcr.io/cthit/bookit@sha256:<digest>' -``` - -Every run uses fresh Testcontainers-managed containers, volumes and a network: -BookIT, Gamma, and separate PostgreSQL/Redis instances for each app. -The BookIT image initializes the empty database before startup, then serves both -the API and built frontend, as in production. -No development servers or existing databases are used. - -Tests log in through Gamma with member/admin/outsider fixtures and reset bookings -and rules between tests. Test data is created through the UI, and assertions check -what users see; tests do not call the API directly. -Completion or failure removes the owned containers, -volumes and network; Testcontainers' reaper handles interrupted runs. Locally built -image tags are removed on exit. Build caches and unrelated Docker resources are -left alone. Published images are pulled on every run. - -Failures retain Playwright traces, screenshots, video and service logs. -CI publishes a commit image after quality checks and runs this same target with -`VERSION`. Fork/Dependabot PRs get quality checks only, without registry credentials; -image E2E requires a reviewed branch in this repository. GitHub releases retag -the tested commit image without rebuilding. - -Development and E2E share `gamma/users.json`. Development uses a stable SQL-seeded -OAuth client; E2E provisions a fresh client through Gamma's UI because each run -uses different ports and credentials. - -Service versions are pinned in `compose.ts`. BookIT's PostgreSQL 12 and Redis 5 -upgrades remain separate follow-ups. - -Manual verification with the pinned Gamma 2.5.1 image found that BookIT's OIDC -sign-out redirect reaches a Gamma HTTP 500 page. Gamma's own Logout button works; -use it before switching test users. The automated suite uses isolated browser -contexts and does not cover this sign-out flow. Resolve and retest the provider's -OIDC logout support before treating local sign-out as verified. - -The existing Prisma schema also gives rules a fixed February 2022 default for -`created_at` and `updated_at`. Newly created rules consequently show that date in -their details. Correcting those database defaults and defining update-time -behavior requires a separate migration; this change leaves the schema intact. diff --git a/e2e/cleanup.spec.ts b/e2e/cleanup.spec.ts index 20c0fc8..509f206 100644 --- a/e2e/cleanup.spec.ts +++ b/e2e/cleanup.spec.ts @@ -2,51 +2,58 @@ import { test, expect } from "./fixtures"; import { createBooking, fillBooking, openBooking } from "./booking-helpers"; import { dateSegments, fillSegments } from "./date-time-helpers"; -test("scheduled cleanup removes old contact data while retaining current bookings", async ({ - page, - environment, -}) => { - await createBooking(page, "E2E current contact"); - await page.getByRole("link", { name: "New booking", exact: true }).click(); - await fillBooking(page, "E2E old contact"); - const oldDate = await page.evaluate(() => { - const date = new Date(); - - date.setDate(date.getDate() - 21); - - return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`; +for (const trigger of ["startup", "command"] as const) { + test(`${trigger} cleanup removes old contact data while retaining current bookings`, async ({ + page, + environment, + }) => { + await createBooking(page, "E2E current contact"); + await page.getByRole("link", { name: "New booking", exact: true }).click(); + await fillBooking(page, "E2E old contact"); + const oldDate = await page.evaluate(() => { + const date = new Date(); + + date.setDate(date.getDate() - 21); + + return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`; + }); + + for (const name of ["Begins at", "Ends at"]) { + await fillSegments(page.getByRole("group", { name, exact: true }), dateSegments(oldDate)); + } + + await page.getByRole("button", { name: "Save booking", exact: true }).click(); + await expect(page).toHaveURL(/\/$/); + if (trigger === "startup") { + await environment.restartBookit(); + } else { + await environment.cleanupPersonalData(); + await environment.cleanupPersonalData(); + } + await page.reload(); + await openBooking(page, "E2E current contact"); + await page.getByRole("button", { name: "Edit", exact: true }).click(); + await expect(page.getByRole("textbox", { name: "Phone number", exact: true })).toHaveValue( + "0701234567", + ); + + await page.goto("/"); + + for (let week = 0; week < 3; week++) { + await page.getByRole("button", { name: /previous/i }).click(); + } + + await openBooking(page, "E2E old contact"); + const details = page.getByRole("dialog", { name: "Booking details", exact: true }); + + await expect( + details.getByText("Booked by", { exact: true }).locator("..").locator("dd"), + ).toHaveText("—"); + await page.getByRole("button", { name: "Edit", exact: true }).click(); + await expect(page.getByRole("textbox", { name: "Phone number", exact: true })).toHaveValue(""); + await expect(page.getByRole("textbox", { name: "Phone number", exact: true })).toHaveAttribute( + "required", + "", + ); }); - - for (const name of ["Begins at", "Ends at"]) { - await fillSegments(page.getByRole("group", { name, exact: true }), dateSegments(oldDate)); - } - - await page.getByRole("button", { name: "Save booking", exact: true }).click(); - await expect(page).toHaveURL(/\/$/); - await environment.cleanupPersonalData(); - await page.reload(); - await openBooking(page, "E2E current contact"); - await page.getByRole("button", { name: "Edit", exact: true }).click(); - await expect(page.getByRole("textbox", { name: "Phone number", exact: true })).toHaveValue( - "0701234567", - ); - - await page.goto("/"); - - for (let week = 0; week < 3; week++) { - await page.getByRole("button", { name: /previous/i }).click(); - } - - await openBooking(page, "E2E old contact"); - const details = page.getByRole("dialog", { name: "Booking details", exact: true }); - - await expect( - details.getByText("Booked by", { exact: true }).locator("..").locator("dd"), - ).toHaveText("—"); - await page.getByRole("button", { name: "Edit", exact: true }).click(); - await expect(page.getByRole("textbox", { name: "Phone number", exact: true })).toHaveValue(""); - await expect(page.getByRole("textbox", { name: "Phone number", exact: true })).toHaveAttribute( - "required", - "", - ); -}); +} diff --git a/e2e/compose.ts b/e2e/compose.ts index e61ebbc..0bbb7b6 100644 --- a/e2e/compose.ts +++ b/e2e/compose.ts @@ -3,7 +3,6 @@ import { PostgreSqlContainer, type StartedPostgreSqlContainer } from "@testconta import { GenericContainer, Network, Wait, type StartedTestContainer } from "testcontainers"; import { createServer, type AddressInfo } from "node:net"; import seed from "../gamma/users.json"; -import { resolve } from "node:path"; export const images = { bookitPostgres: "postgres:12.22-alpine", @@ -40,6 +39,7 @@ export interface Environment { logs: Record<string, string>; resetBookings(): Promise<void>; cleanupPersonalData(): Promise<void>; + restartBookit(): Promise<void>; stop(): Promise<void>; } @@ -316,7 +316,7 @@ export async function compose(browser: Browser): Promise<Environment> { .start(), ); - await track( + const bookit = await track( new GenericContainer(image) .withPlatform("linux/amd64") .withNetwork(network) @@ -335,32 +335,17 @@ export async function compose(browser: Browser): Promise<Environment> { stop, async cleanupPersonalData() { - await bookitDb.copyFilesToContainer([ - { source: resolve("db-scripts/cleanup.sh"), target: "/cleanup/cleanup.sh" }, - { source: resolve("db-scripts/delete-data.sql"), target: "/cleanup/delete-data.sql" }, - ]); + const result = await bookit.exec(["sh", "./startup.sh", "--cleanup"]); - // Exercise both supported deployment configurations against real data. - for (const connection of [ - { DATABASE_URL: "postgresql://bookit_test:bookit_test@127.0.0.1:5432/bookit_test" }, - { - DB_HOST: "127.0.0.1", - DB_PORT: "5432", - DB_USER: "bookit_test", - DB_NAME: "bookit_test", - PGPASSWORD: "bookit_test", - }, - ]) { - const result = await bookitDb.exec(["sh", "/cleanup/cleanup.sh"], { - env: { ...connection, CLEANUP_ONCE: "1" }, - }); - - if (result.exitCode !== 0) { - throw new Error(`Privacy cleanup failed: ${result.output}`); - } + if (result.exitCode !== 0) { + throw new Error(`Privacy cleanup failed: ${result.output}`); } }, + async restartBookit() { + await bookit.restart(); + }, + async resetBookings() { const result = await bookitDb.exec([ "psql", diff --git a/frontend/index.html b/frontend/index.html index 4131eb8..d808f32 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,7 +3,7 @@ <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <meta name="theme-color" content="#2196f3" /> + <meta name="theme-color" content="#09cdda" /> <title>BookIT diff --git a/frontend/package.json b/frontend/package.json index 253e270..0ed0eb6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -34,6 +34,7 @@ "tailwindcss": "^4.3.3", "tw-animate-css": "^1.4.0", "typescript": "^7.0.2", + "vite": "npm:@voidzero-dev/vite-plus-core@0.3.0", "vite-plus": "0.3.0" } } diff --git a/frontend/src/components/booking-form.tsx b/frontend/src/components/booking-form.tsx index 0a7f139..18ea4b5 100644 --- a/frontend/src/components/booking-form.tsx +++ b/frontend/src/components/booking-form.tsx @@ -205,7 +205,7 @@ export function BookingForm({ {rooms.map((room) => (