diff --git a/.env.test b/.env.test new file mode 100644 index 0000000000..130207ecee --- /dev/null +++ b/.env.test @@ -0,0 +1,5 @@ +NODE_ENV=test +DB_HOST=localhost +DB_PORT=5434 +DB_USER=postgres +DB_PASSWORD=example diff --git a/.github/scripts/prepare-test-template-db.sh b/.github/scripts/prepare-test-template-db.sh new file mode 100755 index 0000000000..6acfcc2bf5 --- /dev/null +++ b/.github/scripts/prepare-test-template-db.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +# shellcheck source=scripts/utils +source "${REPO_ROOT}/scripts/utils" + +: "${DB_HOST:?DB_HOST is required}" +: "${DB_PORT:?DB_PORT is required}" +: "${DB_USER:?DB_USER is required}" +: "${DB_PASSWORD:?DB_PASSWORD is required}" + +say "Applying Sequin bootstrap SQL..." +docker run --rm --network host \ + -v "${REPO_ROOT}/scripts/scaffold/sequin/postgres-docker-entrypoint-initdb.d/create-sequin-database.sql:/bootstrap.sql:ro" \ + -e "PGPASSWORD=${DB_PASSWORD}" \ + postgres:14-alpine \ + psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d postgres -v ON_ERROR_STOP=1 -f /bootstrap.sql + +say "Recreating test_template..." +docker run --rm --network host \ + -e "PGPASSWORD=${DB_PASSWORD}" \ + postgres:14-alpine \ + psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d postgres -v ON_ERROR_STOP=1 \ + -c "DROP DATABASE IF EXISTS test_template;" \ + -c "CREATE DATABASE test_template;" + +say "Building flyway image..." +docker build -t crowd_flyway -f "${REPO_ROOT}/backend/src/database/Dockerfile.flyway" "${REPO_ROOT}/backend/src/database" + +say "Migrating test_template..." +docker run --rm --network host \ + -e "PGHOST=${DB_HOST}" \ + -e "PGPORT=${DB_PORT}" \ + -e "PGUSER=${DB_USER}" \ + -e "PGPASSWORD=${DB_PASSWORD}" \ + -e PGDATABASE=test_template \ + crowd_flyway + +say "Test template database ready." diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml new file mode 100644 index 0000000000..2cb2fcfb5c --- /dev/null +++ b/.github/workflows/server-tests.yml @@ -0,0 +1,72 @@ +name: Server Tests + +on: + pull_request: + # Only trigger when relevant files change to save CI minutes + paths-ignore: + - 'frontend/**' + - '**.md' + - '.gitignore' + - '.editorconfig' + - '**/.eslintrc*' + - '.prettierrc' + - '.prettierignore' + - 'LICENSE' + +# Automatically cancel in-progress runs if you push new code to the same PR +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 30 + + services: + postgres: + image: postgres:14-alpine + command: postgres -c wal_level=logical + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: example + POSTGRES_DB: postgres + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U postgres" + --health-interval 2s + --health-timeout 5s + --health-retries 15 + + env: + NODE_ENV: test + DB_HOST: localhost + DB_PORT: 5432 + DB_USER: postgres + DB_PASSWORD: example + + steps: + - name: Check out repository code + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'pnpm' + + - name: Install dependencies + run: pnpm i --frozen-lockfile + + - name: Prepare test database + run: ./.github/scripts/prepare-test-template-db.sh + + - name: Run server tests + run: pnpm test:server diff --git a/backend/package.json b/backend/package.json index 94d27bb3d9..c1eec8fa5d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -93,7 +93,6 @@ "dotenv": "8.2.0", "dotenv-expand": "^8.0.3", "emoji-dictionary": "^1.0.11", - "erlpack": "^0.1.4", "express": "4.17.1", "express-oauth2-jwt-bearer": "^1.7.4", "express-rate-limit": "6.5.1", @@ -132,7 +131,6 @@ "uuid": "^9.0.0", "validator": "^13.7.0", "verify-github-webhook": "^1.0.1", - "zlib-sync": "^0.1.8", "zod": "^4.3.6" }, "private": true, diff --git a/package.json b/package.json index c6179e1862..9de1a22101 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,18 @@ { "private": true, "scripts": { - "prepare": "husky" + "prepare": "husky", + "test": "vitest run", + "test:server": "vitest run --project server", + "test:changed": "vitest --changed", + "test:watch": "vitest" }, "devDependencies": { "@commitlint/cli": "^19.8.0", "@commitlint/config-conventional": "^19.8.0", - "husky": "^9.1.7" + "husky": "^9.1.7", + "vite": "6.3.5", + "vitest": "4.1.7" }, "packageManager": "pnpm@9.11.0+sha512.0a203ffaed5a3f63242cd064c8fb5892366c103e328079318f78062f24ea8c9d50bc6a47aa3567cabefd824d170e78fa2745ed1f16b132e16436146b7688f19b" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6b8822f0f4..895f85d489 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,12 @@ importers: husky: specifier: ^9.1.7 version: 9.1.7 + vite: + specifier: 6.3.5 + version: 6.3.5(@types/node@22.19.10)(jiti@2.4.2)(terser@5.43.1)(yaml@2.7.0) + vitest: + specifier: 4.1.7 + version: 4.1.7(@types/node@22.19.10)(vite@6.3.5(@types/node@22.19.10)(jiti@2.4.2)(terser@5.43.1)(yaml@2.7.0)) .github/actions/node: dependencies: @@ -222,9 +228,6 @@ importers: emoji-dictionary: specifier: ^1.0.11 version: 1.0.11 - erlpack: - specifier: ^0.1.4 - version: 0.1.4 express: specifier: 4.17.1 version: 4.17.1 @@ -339,9 +342,6 @@ importers: verify-github-webhook: specifier: ^1.0.1 version: 1.0.1 - zlib-sync: - specifier: ^0.1.8 - version: 0.1.9 zod: specifier: ^4.3.6 version: 4.3.6 @@ -1348,7 +1348,7 @@ importers: version: 3.1.0 vitest: specifier: ^3.2.4 - version: 3.2.4(@types/debug@4.1.12)(@types/node@20.12.7)(terser@5.43.1) + version: 3.2.4(@types/debug@4.1.12)(@types/node@20.12.7)(jiti@2.4.2)(terser@5.43.1)(tsx@4.7.3)(yaml@2.7.0) services/apps/pcc_sync_worker: dependencies: @@ -2232,9 +2232,6 @@ importers: typescript: specifier: ^5.6.3 version: 5.6.3 - vitest: - specifier: ^3.2.4 - version: 3.2.4(@types/debug@4.1.12)(@types/node@18.19.31)(terser@5.43.1) services/libs/database: dependencies: @@ -2553,6 +2550,28 @@ importers: specifier: ^5.6.3 version: 5.6.3 + services/libs/test-kit: + dependencies: + '@crowd/common': + specifier: workspace:* + version: link:../common + '@crowd/database': + specifier: workspace:* + version: link:../database + pg-promise: + specifier: ^11.4.3 + version: 11.6.0 + devDependencies: + '@types/node': + specifier: ^20.0.0 + version: 20.12.7 + typescript: + specifier: ^5.6.3 + version: 5.6.3 + vitest: + specifier: 4.1.7 + version: 4.1.7(@types/node@20.12.7)(vite@6.3.5(@types/node@20.12.7)(jiti@2.4.2)(terser@5.43.1)(yaml@2.7.0)) + services/libs/types: devDependencies: '@types/node': @@ -3268,9 +3287,9 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} - engines: {node: '>=12'} + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} cpu: [ppc64] os: [aix] @@ -3280,9 +3299,9 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} - engines: {node: '>=12'} + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} cpu: [arm64] os: [android] @@ -3292,9 +3311,9 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} - engines: {node: '>=12'} + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} cpu: [arm] os: [android] @@ -3304,9 +3323,9 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} - engines: {node: '>=12'} + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} cpu: [x64] os: [android] @@ -3316,9 +3335,9 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} - engines: {node: '>=12'} + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} cpu: [arm64] os: [darwin] @@ -3328,9 +3347,9 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} - engines: {node: '>=12'} + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} cpu: [x64] os: [darwin] @@ -3340,9 +3359,9 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} - engines: {node: '>=12'} + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} cpu: [arm64] os: [freebsd] @@ -3352,9 +3371,9 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} - engines: {node: '>=12'} + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} cpu: [x64] os: [freebsd] @@ -3364,9 +3383,9 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} - engines: {node: '>=12'} + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} cpu: [arm64] os: [linux] @@ -3376,9 +3395,9 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} - engines: {node: '>=12'} + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} cpu: [arm] os: [linux] @@ -3388,9 +3407,9 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} - engines: {node: '>=12'} + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} cpu: [ia32] os: [linux] @@ -3400,9 +3419,9 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} - engines: {node: '>=12'} + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} cpu: [loong64] os: [linux] @@ -3412,9 +3431,9 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} - engines: {node: '>=12'} + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} cpu: [mips64el] os: [linux] @@ -3424,9 +3443,9 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} - engines: {node: '>=12'} + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} cpu: [ppc64] os: [linux] @@ -3436,9 +3455,9 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} - engines: {node: '>=12'} + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} cpu: [riscv64] os: [linux] @@ -3448,9 +3467,9 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} - engines: {node: '>=12'} + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} cpu: [s390x] os: [linux] @@ -3460,45 +3479,63 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} - engines: {node: '>=12'} + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.19.12': resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} - engines: {node: '>=12'} + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.19.12': resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} - engines: {node: '>=12'} + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.19.12': resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} engines: {node: '>=12'} cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} - engines: {node: '>=12'} + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} cpu: [x64] os: [sunos] @@ -3508,9 +3545,9 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} - engines: {node: '>=12'} + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} cpu: [arm64] os: [win32] @@ -3520,9 +3557,9 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} - engines: {node: '>=12'} + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} cpu: [ia32] os: [win32] @@ -3532,9 +3569,9 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} - engines: {node: '>=12'} + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -4568,6 +4605,9 @@ packages: '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@swc/core-darwin-arm64@1.4.17': resolution: {integrity: sha512-HVl+W4LezoqHBAYg2JCqR+s9ife9yPfgWSj37iIawLWzOmuuJ7jVdIB7Ee2B75bEisSEKyxRlTl6Y1Oq3owBgw==} engines: {node: '>=10'} @@ -4985,7 +5025,6 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - deprecated: Potential CWE-502 - Update to 1.3.1 or higher '@vercel/ncc@0.38.1': resolution: {integrity: sha512-IBBb+iI2NLu4VQn3Vwldyi2QwaXt5+hTyh58ggAMoCGE6DJmPvwL3KPBWcJl1m9LYPChBLE980Jw+CS4Wokqxw==} @@ -4994,6 +5033,9 @@ packages: '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + '@vitest/expect@4.1.7': + resolution: {integrity: sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==} + '@vitest/mocker@3.2.4': resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} peerDependencies: @@ -5005,21 +5047,47 @@ packages: vite: optional: true + '@vitest/mocker@4.1.7': + resolution: {integrity: sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==} + 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@3.2.4': resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + '@vitest/pretty-format@4.1.7': + resolution: {integrity: sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==} + '@vitest/runner@3.2.4': resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + '@vitest/runner@4.1.7': + resolution: {integrity: sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==} + '@vitest/snapshot@3.2.4': resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + '@vitest/snapshot@4.1.7': + resolution: {integrity: sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==} + '@vitest/spy@3.2.4': resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + '@vitest/spy@4.1.7': + resolution: {integrity: sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==} + '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@vitest/utils@4.1.7': + resolution: {integrity: sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==} + '@webassemblyjs/ast@1.14.1': resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} @@ -5396,9 +5464,6 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - bindings@1.5.0: - resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} - bintrees@1.0.2: resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==} @@ -5568,6 +5633,10 @@ packages: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -5793,6 +5862,9 @@ packages: engines: {node: '>=16'} hasBin: true + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} @@ -6137,7 +6209,6 @@ packages: dottie@2.0.6: resolution: {integrity: sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. dtrace-provider@0.8.8: resolution: {integrity: sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==} @@ -6257,9 +6328,6 @@ packages: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} - erlpack@0.1.4: - resolution: {integrity: sha512-CJYbkEvsB5FqCCu2tLxF1eYKi28PvemC12oqzJ9oO6mDFrFO9G9G7nNJUHhiAyyL9zfXTOJx/tOcrQk+ncD65w==} - error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -6282,6 +6350,9 @@ packages: es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-module-lexer@2.1.0: + resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + es-object-atoms@1.0.0: resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} engines: {node: '>= 0.4'} @@ -6323,9 +6394,9 @@ packages: engines: {node: '>=12'} hasBin: true - esbuild@0.21.5: - resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} - engines: {node: '>=12'} + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} hasBin: true escalade@3.1.2: @@ -6643,9 +6714,6 @@ packages: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} - file-uri-to-path@1.0.0: - resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -8285,6 +8353,9 @@ packages: resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} engines: {node: '>= 0.4'} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + omit-deep-by-values@1.0.2: resolution: {integrity: sha512-+EptGOxiWlwhXyBJK9/UZu8k4LQfTmgCkFj/lrAyUBxAcyF174oFfYbkCk2hcZE1d8D+G4e0YTV2qYzCNCEnmw==} engines: {node: '>=0.10.0'} @@ -9352,6 +9423,9 @@ packages: std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + stream-events@1.0.5: resolution: {integrity: sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==} @@ -9584,6 +9658,10 @@ packages: tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyexec@1.2.2: + resolution: {integrity: sha512-M/Q0B2cp4K7kynaT/vnED1j8TlLY+Pp7C6Wl2bl/7u/F0mUVwdyOpwomQb8JpYLitHUssAJRmLZdMCGsrx7i+g==} + engines: {node: '>=18'} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -9596,6 +9674,10 @@ packages: resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + tinyspy@4.0.4: resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} engines: {node: '>=14.0.0'} @@ -9871,17 +9953,15 @@ packages: uuid@3.3.2: resolution: {integrity: sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==} - deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). + 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. hasBin: true uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} - deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true v8-compile-cache-lib@3.0.1: @@ -9909,22 +9989,27 @@ packages: engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true - vite@5.4.21: - resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} - engines: {node: ^18.0.0 || >=20.0.0} + vite@6.3.5: + resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' less: '*' lightningcss: ^1.21.0 sass: '*' sass-embedded: '*' stylus: '*' sugarss: '*' - terser: ^5.4.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 peerDependenciesMeta: '@types/node': optional: true + jiti: + optional: true less: optional: true lightningcss: @@ -9939,6 +10024,10 @@ packages: optional: true terser: optional: true + tsx: + optional: true + yaml: + optional: true vitest@3.2.4: resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} @@ -9968,6 +10057,47 @@ packages: jsdom: optional: true + vitest@4.1.7: + resolution: {integrity: sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==} + 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.7 + '@vitest/browser-preview': 4.1.7 + '@vitest/browser-webdriverio': 4.1.7 + '@vitest/coverage-istanbul': 4.1.7 + '@vitest/coverage-v8': 4.1.7 + '@vitest/ui': 4.1.7 + 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 + watchpack@2.4.4: resolution: {integrity: sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==} engines: {node: '>=10.13.0'} @@ -10202,9 +10332,6 @@ packages: resolution: {integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==} engines: {node: '>=12.20'} - zlib-sync@0.1.9: - resolution: {integrity: sha512-DinB43xCjVwIBDpaIvQqHbmDsnYnSt6HJ/yiB2MZQGTqgPcwBSZqLkimXwK8BvdjQ/MaZysb5uEenImncqvCqQ==} - zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} @@ -11967,139 +12094,148 @@ snapshots: '@esbuild/aix-ppc64@0.19.12': optional: true - '@esbuild/aix-ppc64@0.21.5': + '@esbuild/aix-ppc64@0.25.12': optional: true '@esbuild/android-arm64@0.19.12': optional: true - '@esbuild/android-arm64@0.21.5': + '@esbuild/android-arm64@0.25.12': optional: true '@esbuild/android-arm@0.19.12': optional: true - '@esbuild/android-arm@0.21.5': + '@esbuild/android-arm@0.25.12': optional: true '@esbuild/android-x64@0.19.12': optional: true - '@esbuild/android-x64@0.21.5': + '@esbuild/android-x64@0.25.12': optional: true '@esbuild/darwin-arm64@0.19.12': optional: true - '@esbuild/darwin-arm64@0.21.5': + '@esbuild/darwin-arm64@0.25.12': optional: true '@esbuild/darwin-x64@0.19.12': optional: true - '@esbuild/darwin-x64@0.21.5': + '@esbuild/darwin-x64@0.25.12': optional: true '@esbuild/freebsd-arm64@0.19.12': optional: true - '@esbuild/freebsd-arm64@0.21.5': + '@esbuild/freebsd-arm64@0.25.12': optional: true '@esbuild/freebsd-x64@0.19.12': optional: true - '@esbuild/freebsd-x64@0.21.5': + '@esbuild/freebsd-x64@0.25.12': optional: true '@esbuild/linux-arm64@0.19.12': optional: true - '@esbuild/linux-arm64@0.21.5': + '@esbuild/linux-arm64@0.25.12': optional: true '@esbuild/linux-arm@0.19.12': optional: true - '@esbuild/linux-arm@0.21.5': + '@esbuild/linux-arm@0.25.12': optional: true '@esbuild/linux-ia32@0.19.12': optional: true - '@esbuild/linux-ia32@0.21.5': + '@esbuild/linux-ia32@0.25.12': optional: true '@esbuild/linux-loong64@0.19.12': optional: true - '@esbuild/linux-loong64@0.21.5': + '@esbuild/linux-loong64@0.25.12': optional: true '@esbuild/linux-mips64el@0.19.12': optional: true - '@esbuild/linux-mips64el@0.21.5': + '@esbuild/linux-mips64el@0.25.12': optional: true '@esbuild/linux-ppc64@0.19.12': optional: true - '@esbuild/linux-ppc64@0.21.5': + '@esbuild/linux-ppc64@0.25.12': optional: true '@esbuild/linux-riscv64@0.19.12': optional: true - '@esbuild/linux-riscv64@0.21.5': + '@esbuild/linux-riscv64@0.25.12': optional: true '@esbuild/linux-s390x@0.19.12': optional: true - '@esbuild/linux-s390x@0.21.5': + '@esbuild/linux-s390x@0.25.12': optional: true '@esbuild/linux-x64@0.19.12': optional: true - '@esbuild/linux-x64@0.21.5': + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': optional: true '@esbuild/netbsd-x64@0.19.12': optional: true - '@esbuild/netbsd-x64@0.21.5': + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': optional: true '@esbuild/openbsd-x64@0.19.12': optional: true - '@esbuild/openbsd-x64@0.21.5': + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': optional: true '@esbuild/sunos-x64@0.19.12': optional: true - '@esbuild/sunos-x64@0.21.5': + '@esbuild/sunos-x64@0.25.12': optional: true '@esbuild/win32-arm64@0.19.12': optional: true - '@esbuild/win32-arm64@0.21.5': + '@esbuild/win32-arm64@0.25.12': optional: true '@esbuild/win32-ia32@0.19.12': optional: true - '@esbuild/win32-ia32@0.21.5': + '@esbuild/win32-ia32@0.25.12': optional: true '@esbuild/win32-x64@0.19.12': optional: true - '@esbuild/win32-x64@0.21.5': + '@esbuild/win32-x64@0.25.12': optional: true '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': @@ -12235,7 +12371,7 @@ snapshots: '@grpc/proto-loader@0.7.13': dependencies: lodash.camelcase: 4.3.0 - long: 5.3.2 + long: 5.2.3 protobufjs: 7.5.3 yargs: 17.7.2 @@ -13536,6 +13672,8 @@ snapshots: '@socket.io/component-emitter@3.1.2': {} + '@standard-schema/spec@1.1.0': {} + '@swc/core-darwin-arm64@1.4.17': optional: true @@ -13611,7 +13749,7 @@ snapshots: '@temporalio/common': 1.11.8 '@temporalio/proto': 1.11.8 abort-controller: 3.0.0 - long: 5.3.2 + long: 5.2.3 uuid: 9.0.1 '@temporalio/common@1.11.8': @@ -14060,48 +14198,89 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@5.4.21(@types/node@18.19.31)(terser@5.43.1))': + '@vitest/expect@4.1.7': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.7 + '@vitest/utils': 4.1.7 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@3.2.4(vite@6.3.5(@types/node@20.12.7)(jiti@2.4.2)(terser@5.43.1)(tsx@4.7.3)(yaml@2.7.0))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 5.4.21(@types/node@18.19.31)(terser@5.43.1) + vite: 6.3.5(@types/node@20.12.7)(jiti@2.4.2)(terser@5.43.1)(tsx@4.7.3)(yaml@2.7.0) - '@vitest/mocker@3.2.4(vite@5.4.21(@types/node@20.12.7)(terser@5.43.1))': + '@vitest/mocker@4.1.7(vite@6.3.5(@types/node@20.12.7)(jiti@2.4.2)(terser@5.43.1)(yaml@2.7.0))': dependencies: - '@vitest/spy': 3.2.4 + '@vitest/spy': 4.1.7 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 6.3.5(@types/node@20.12.7)(jiti@2.4.2)(terser@5.43.1)(tsx@4.7.3)(yaml@2.7.0) + + '@vitest/mocker@4.1.7(vite@6.3.5(@types/node@22.19.10)(jiti@2.4.2)(terser@5.43.1)(yaml@2.7.0))': + dependencies: + '@vitest/spy': 4.1.7 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 5.4.21(@types/node@20.12.7)(terser@5.43.1) + vite: 6.3.5(@types/node@22.19.10)(jiti@2.4.2)(terser@5.43.1)(yaml@2.7.0) '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 + '@vitest/pretty-format@4.1.7': + dependencies: + tinyrainbow: 3.1.0 + '@vitest/runner@3.2.4': dependencies: '@vitest/utils': 3.2.4 pathe: 2.0.3 strip-literal: 3.1.0 + '@vitest/runner@4.1.7': + dependencies: + '@vitest/utils': 4.1.7 + pathe: 2.0.3 + '@vitest/snapshot@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 magic-string: 0.30.21 pathe: 2.0.3 + '@vitest/snapshot@4.1.7': + dependencies: + '@vitest/pretty-format': 4.1.7 + '@vitest/utils': 4.1.7 + magic-string: 0.30.21 + pathe: 2.0.3 + '@vitest/spy@3.2.4': dependencies: tinyspy: 4.0.4 + '@vitest/spy@4.1.7': {} + '@vitest/utils@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 loupe: 3.2.1 tinyrainbow: 2.0.0 + '@vitest/utils@4.1.7': + dependencies: + '@vitest/pretty-format': 4.1.7 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + '@webassemblyjs/ast@1.14.1': dependencies: '@webassemblyjs/helper-numbers': 1.13.2 @@ -14553,10 +14732,6 @@ snapshots: binary-extensions@2.3.0: {} - bindings@1.5.0: - dependencies: - file-uri-to-path: 1.0.0 - bintrees@1.0.2: {} bl@4.1.0: @@ -14781,6 +14956,8 @@ snapshots: loupe: 3.2.1 pathval: 2.0.1 + chai@6.2.2: {} + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -15031,6 +15208,8 @@ snapshots: meow: 12.1.1 split2: 4.2.0 + convert-source-map@2.0.0: {} + cookie-signature@1.0.6: {} cookie@0.4.0: {} @@ -15492,11 +15671,6 @@ snapshots: environment@1.1.0: {} - erlpack@0.1.4: - dependencies: - bindings: 1.5.0 - nan: 2.19.0 - error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 @@ -15560,6 +15734,8 @@ snapshots: es-module-lexer@1.7.0: {} + es-module-lexer@2.1.0: {} + es-object-atoms@1.0.0: dependencies: es-errors: 1.3.0 @@ -15638,31 +15814,34 @@ snapshots: '@esbuild/win32-ia32': 0.19.12 '@esbuild/win32-x64': 0.19.12 - esbuild@0.21.5: + esbuild@0.25.12: optionalDependencies: - '@esbuild/aix-ppc64': 0.21.5 - '@esbuild/android-arm': 0.21.5 - '@esbuild/android-arm64': 0.21.5 - '@esbuild/android-x64': 0.21.5 - '@esbuild/darwin-arm64': 0.21.5 - '@esbuild/darwin-x64': 0.21.5 - '@esbuild/freebsd-arm64': 0.21.5 - '@esbuild/freebsd-x64': 0.21.5 - '@esbuild/linux-arm': 0.21.5 - '@esbuild/linux-arm64': 0.21.5 - '@esbuild/linux-ia32': 0.21.5 - '@esbuild/linux-loong64': 0.21.5 - '@esbuild/linux-mips64el': 0.21.5 - '@esbuild/linux-ppc64': 0.21.5 - '@esbuild/linux-riscv64': 0.21.5 - '@esbuild/linux-s390x': 0.21.5 - '@esbuild/linux-x64': 0.21.5 - '@esbuild/netbsd-x64': 0.21.5 - '@esbuild/openbsd-x64': 0.21.5 - '@esbuild/sunos-x64': 0.21.5 - '@esbuild/win32-arm64': 0.21.5 - '@esbuild/win32-ia32': 0.21.5 - '@esbuild/win32-x64': 0.21.5 + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 escalade@3.1.2: {} @@ -16089,8 +16268,6 @@ snapshots: dependencies: flat-cache: 3.2.0 - file-uri-to-path@1.0.0: {} - fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -17528,7 +17705,8 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - nan@2.19.0: {} + nan@2.19.0: + optional: true nanoid@3.3.11: {} @@ -17817,6 +17995,8 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.0.0 + obug@2.1.1: {} + omit-deep-by-values@1.0.2: dependencies: lodash: 4.17.21 @@ -18264,7 +18444,7 @@ snapshots: '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 '@types/node': 20.12.7 - long: 5.3.2 + long: 5.2.3 protobufjs@7.5.3: dependencies: @@ -18279,7 +18459,7 @@ snapshots: '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 '@types/node': 20.12.7 - long: 5.3.2 + long: 5.2.3 proxy-addr@2.0.7: dependencies: @@ -19054,6 +19234,8 @@ snapshots: std-env@3.10.0: {} + std-env@4.1.0: {} + stream-events@1.0.5: dependencies: stubs: 3.0.0 @@ -19329,6 +19511,8 @@ snapshots: tinyexec@0.3.2: {} + tinyexec@1.2.2: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.4) @@ -19338,6 +19522,8 @@ snapshots: tinyrainbow@2.0.0: {} + tinyrainbow@3.1.0: {} + tinyspy@4.0.4: {} tldts-core@6.1.18: {} @@ -19645,33 +19831,16 @@ snapshots: verify-github-webhook@1.0.1: {} - vite-node@3.2.4(@types/node@18.19.31)(terser@5.43.1): - dependencies: - cac: 6.7.14 - debug: 4.4.3 - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 5.4.21(@types/node@18.19.31)(terser@5.43.1) - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - vite-node@3.2.4(@types/node@20.12.7)(terser@5.43.1): + vite-node@3.2.4(@types/node@20.12.7)(jiti@2.4.2)(terser@5.43.1)(tsx@4.7.3)(yaml@2.7.0): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 5.4.21(@types/node@20.12.7)(terser@5.43.1) + vite: 6.3.5(@types/node@20.12.7)(jiti@2.4.2)(terser@5.43.1)(tsx@4.7.3)(yaml@2.7.0) transitivePeerDependencies: - '@types/node' + - jiti - less - lightningcss - sass @@ -19680,32 +19849,45 @@ snapshots: - sugarss - supports-color - terser + - tsx + - yaml - vite@5.4.21(@types/node@18.19.31)(terser@5.43.1): + vite@6.3.5(@types/node@20.12.7)(jiti@2.4.2)(terser@5.43.1)(tsx@4.7.3)(yaml@2.7.0): dependencies: - esbuild: 0.21.5 + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 postcss: 8.5.8 rollup: 4.60.1 + tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 18.19.31 + '@types/node': 20.12.7 fsevents: 2.3.3 + jiti: 2.4.2 terser: 5.43.1 + tsx: 4.7.3 + yaml: 2.7.0 - vite@5.4.21(@types/node@20.12.7)(terser@5.43.1): + vite@6.3.5(@types/node@22.19.10)(jiti@2.4.2)(terser@5.43.1)(yaml@2.7.0): dependencies: - esbuild: 0.21.5 + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 postcss: 8.5.8 rollup: 4.60.1 + tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 20.12.7 + '@types/node': 22.19.10 fsevents: 2.3.3 + jiti: 2.4.2 terser: 5.43.1 + yaml: 2.7.0 - vitest@3.2.4(@types/debug@4.1.12)(@types/node@18.19.31)(terser@5.43.1): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.12.7)(jiti@2.4.2)(terser@5.43.1)(tsx@4.7.3)(yaml@2.7.0): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@5.4.21(@types/node@18.19.31)(terser@5.43.1)) + '@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@20.12.7)(jiti@2.4.2)(terser@5.43.1)(tsx@4.7.3)(yaml@2.7.0)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -19723,13 +19905,14 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 5.4.21(@types/node@18.19.31)(terser@5.43.1) - vite-node: 3.2.4(@types/node@18.19.31)(terser@5.43.1) + vite: 6.3.5(@types/node@20.12.7)(jiti@2.4.2)(terser@5.43.1)(tsx@4.7.3)(yaml@2.7.0) + vite-node: 3.2.4(@types/node@20.12.7)(jiti@2.4.2)(terser@5.43.1)(tsx@4.7.3)(yaml@2.7.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 - '@types/node': 18.19.31 + '@types/node': 20.12.7 transitivePeerDependencies: + - jiti - less - lightningcss - msw @@ -19739,45 +19922,62 @@ snapshots: - sugarss - supports-color - terser + - tsx + - yaml + + vitest@4.1.7(@types/node@20.12.7)(vite@6.3.5(@types/node@20.12.7)(jiti@2.4.2)(terser@5.43.1)(yaml@2.7.0)): + dependencies: + '@vitest/expect': 4.1.7 + '@vitest/mocker': 4.1.7(vite@6.3.5(@types/node@20.12.7)(jiti@2.4.2)(terser@5.43.1)(yaml@2.7.0)) + '@vitest/pretty-format': 4.1.7 + '@vitest/runner': 4.1.7 + '@vitest/snapshot': 4.1.7 + '@vitest/spy': 4.1.7 + '@vitest/utils': 4.1.7 + es-module-lexer: 2.1.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.2.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.1.0 + vite: 6.3.5(@types/node@20.12.7)(jiti@2.4.2)(terser@5.43.1)(tsx@4.7.3)(yaml@2.7.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 20.12.7 + transitivePeerDependencies: + - msw - vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.12.7)(terser@5.43.1): + vitest@4.1.7(@types/node@22.19.10)(vite@6.3.5(@types/node@22.19.10)(jiti@2.4.2)(terser@5.43.1)(yaml@2.7.0)): dependencies: - '@types/chai': 5.2.3 - '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@5.4.21(@types/node@20.12.7)(terser@5.43.1)) - '@vitest/pretty-format': 3.2.4 - '@vitest/runner': 3.2.4 - '@vitest/snapshot': 3.2.4 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.3.3 - debug: 4.4.3 + '@vitest/expect': 4.1.7 + '@vitest/mocker': 4.1.7(vite@6.3.5(@types/node@22.19.10)(jiti@2.4.2)(terser@5.43.1)(yaml@2.7.0)) + '@vitest/pretty-format': 4.1.7 + '@vitest/runner': 4.1.7 + '@vitest/snapshot': 4.1.7 + '@vitest/spy': 4.1.7 + '@vitest/utils': 4.1.7 + es-module-lexer: 2.1.0 expect-type: 1.3.0 magic-string: 0.30.21 + obug: 2.1.1 pathe: 2.0.3 picomatch: 4.0.4 - std-env: 3.10.0 + std-env: 4.1.0 tinybench: 2.9.0 - tinyexec: 0.3.2 + tinyexec: 1.2.2 tinyglobby: 0.2.15 - tinypool: 1.1.1 - tinyrainbow: 2.0.0 - vite: 5.4.21(@types/node@20.12.7)(terser@5.43.1) - vite-node: 3.2.4(@types/node@20.12.7)(terser@5.43.1) + tinyrainbow: 3.1.0 + vite: 6.3.5(@types/node@22.19.10)(jiti@2.4.2)(terser@5.43.1)(yaml@2.7.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/debug': 4.1.12 - '@types/node': 20.12.7 + '@types/node': 22.19.10 transitivePeerDependencies: - - less - - lightningcss - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser watchpack@2.4.4: dependencies: @@ -20062,8 +20262,4 @@ snapshots: yocto-queue@1.2.1: {} - zlib-sync@0.1.9: - dependencies: - nan: 2.19.0 - zod@4.3.6: {} diff --git a/scripts/cli b/scripts/cli index 9f3ce75aac..2a09510ebf 100755 --- a/scripts/cli +++ b/scripts/cli @@ -429,6 +429,10 @@ function scaffold() { up_test_scaffold exit ;; + down-test) + down_test_scaffold + exit + ;; *) error "Invalid command '$1'" && say "$HELP" exit 1 @@ -774,22 +778,29 @@ function migrate_local() { function up_test_scaffold() { scaffold_set_up_network "${PROJECT_NAME}-bridge-test" $DOCKET_TEST_NETWORK_SUBNET $DOCKER_TEST_NETWORK_GATEWAY - $_DC -p "$PROJECT_NAME-test" -f $CLI_HOME/../backend/docker-compose.test.yaml down - $_DC -p "$PROJECT_NAME-test" -f $CLI_HOME/../backend/docker-compose.test.yaml up -d + $_DC -p "$PROJECT_NAME-test" -f $CLI_HOME/scaffold.test.yaml up -d migrate_test } -function migrate_test() { - say "Building flyway migration image..." - docker build -t crowd_flyway -f $CLI_HOME/../backend/src/database/Dockerfile.flyway $CLI_HOME/../backend/src/database +function down_test_scaffold() { + $_DC -p "$PROJECT_NAME-test" -f $CLI_HOME/scaffold.test.yaml down +} - say "Applying database migrations!" +function migrate_test() { + wait_for_postgres test-db crowd-test-db + say "Recreating test_template..." + docker exec crowd-test-db psql -U postgres -d postgres -v ON_ERROR_STOP=1 \ + -c "DROP DATABASE IF EXISTS test_template;" \ + -c "CREATE DATABASE test_template;" + say "Building crowd flyway migration image..." + docker build $DOCKER_PLATFORM_FLAGS -t crowd_flyway -f $CLI_HOME/../backend/src/database/Dockerfile.flyway $CLI_HOME/../backend/src/database --load + say "Migrating test_template..." docker run --rm --network "${PROJECT_NAME}-bridge-test" \ - -e PGHOST=db-test \ + -e PGHOST=crowd-test-db \ -e PGPORT=5432 \ -e PGUSER=postgres \ -e PGPASSWORD=example \ - -e PGDATABASE=crowd-web \ + -e PGDATABASE=test_template \ crowd_flyway } @@ -1003,7 +1014,7 @@ function start() { SCRIPT_USAGE="${YELLOW}${PROJECT_NAME} CLI ${RESET}\n Usage: ./cli \n ${YELLOW}Scaffold:${RESET} - scaffold [up|down|destroy|reset|create-migration|create-product-migration|create-packages-migration|migrate-up|up-test]\n + scaffold [up|down|destroy|reset|create-migration|create-product-migration|create-packages-migration|migrate-up|up-test|down-test]\n ${YELLOW}Services:${RESET} service [up|down|restart|status|logs|id] start | start-dev | start-be | start-e2e diff --git a/scripts/scaffold.test.yaml b/scripts/scaffold.test.yaml new file mode 100644 index 0000000000..c18ae2a038 --- /dev/null +++ b/scripts/scaffold.test.yaml @@ -0,0 +1,25 @@ +services: + test-db: + image: postgres:14-alpine + container_name: crowd-test-db + restart: unless-stopped + command: -c 'wal_level=logical' + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: example + POSTGRES_DB: postgres + ports: + - '5434:5432' + volumes: + - ./scaffold/sequin/postgres-docker-entrypoint-initdb.d/create-sequin-database.sql:/docker-entrypoint-initdb.d/create-sequin-database.sql + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U postgres'] + interval: 2s + timeout: 5s + retries: 15 + networks: + - crowd-bridge-test + +networks: + crowd-bridge-test: + external: true diff --git a/services/apps/data_sink_worker/src/bin/fix-member-attributes.ts b/services/apps/data_sink_worker/src/bin/fix-member-attributes.ts index 302adbfc3f..8e414c5ae4 100644 --- a/services/apps/data_sink_worker/src/bin/fix-member-attributes.ts +++ b/services/apps/data_sink_worker/src/bin/fix-member-attributes.ts @@ -1,7 +1,7 @@ import isEqual from 'lodash.isequal' import mergeWith from 'lodash.mergewith' -import { connQx, updateMember } from '@crowd/data-access-layer' +import { pgpQx, updateMember } from '@crowd/data-access-layer' import { DbConnOrTx, DbStore, @@ -98,7 +98,7 @@ setImmediate(async () => { redisClient = await getRedisClient(REDIS_CONFIG()) log.info('Redis connection established') - const pgQx = connQx(dbClient) + const pgQx = pgpQx(dbClient) const mas = new MemberAttributeService(redisClient, new DbStore(log, dbClient), log) let totalProcessed = 0 diff --git a/services/libs/data-access-layer/package.json b/services/libs/data-access-layer/package.json index a373ea5142..8e2c473d68 100644 --- a/services/libs/data-access-layer/package.json +++ b/services/libs/data-access-layer/package.json @@ -3,9 +3,6 @@ "private": true, "main": "src/index.ts", "scripts": { - "test": "vitest run", - "test:affiliations": "vitest run src/affiliations", - "test:affiliations:watch": "vitest src/affiliations", "lint": "npx eslint --ext .ts src --max-warnings=0", "format": "npx prettier --write \"src/**/*.ts\"", "format-check": "npx prettier --check .", @@ -38,7 +35,6 @@ "devDependencies": { "@types/node": "^18.16.3", "sequelize": "6.37.8", - "typescript": "^5.6.3", - "vitest": "^3.2.4" + "typescript": "^5.6.3" } } diff --git a/services/libs/data-access-layer/src/auditLogs/index.ts b/services/libs/data-access-layer/src/auditLogs/index.ts index c6d690bed3..803ac3e362 100644 --- a/services/libs/data-access-layer/src/auditLogs/index.ts +++ b/services/libs/data-access-layer/src/auditLogs/index.ts @@ -2,7 +2,7 @@ import validator from 'validator' import { WRITE_DB_CONFIG, getDbConnection } from '@crowd/database' -import { QueryExecutor, connQx } from '../queryExecutor' +import { QueryExecutor, pgpQx } from '../queryExecutor' export enum ActorType { USER = 'user', @@ -75,7 +75,7 @@ let qx: QueryExecutor | undefined = undefined export async function addAuditAction(options: AuditLogRequestOptions, action: AuditLogAction) { if (!qx) { const conn = await getDbConnection(WRITE_DB_CONFIG()) - qx = connQx(conn) + qx = pgpQx(conn) } await qx.result( diff --git a/services/libs/data-access-layer/src/queryExecutor.ts b/services/libs/data-access-layer/src/queryExecutor.ts index 298b997507..ad9b3892ce 100644 --- a/services/libs/data-access-layer/src/queryExecutor.ts +++ b/services/libs/data-access-layer/src/queryExecutor.ts @@ -1,24 +1,13 @@ -import pgp from 'pg-promise' import { QueryTypes, Sequelize, Transaction } from 'sequelize' -import { DbConnOrTx, DbConnection, DbStore, DbTransaction, RepositoryBase } from '@crowd/database' +import { type QueryExecutor, formatQuery } from '@crowd/database' -/* eslint-disable @typescript-eslint/no-explicit-any */ - -export interface QueryExecutor { - select(query: string, params?: object): Promise - selectNone(query: string, params?: object): Promise - selectOneOrNone(query: string, params?: object): Promise - selectOne(query: string, params?: object): Promise - result(query: string, params?: object): Promise - - tx(fn: (tx: QueryExecutor) => Promise): Promise -} +export { PgPromiseQueryExecutor, dbStoreQx, formatQuery, pgpQx, repoQx } from '@crowd/database' +export type { QueryExecutor } from '@crowd/database' -export function formatQuery(query: string, params?: object): string { - return pgp.as.format(query, params) -} +/* eslint-disable @typescript-eslint/no-explicit-any */ +/** Sequelize-backed QueryExecutor for legacy backend repositories. */ export class SequelizeQueryExecutor implements QueryExecutor { constructor(private readonly sequelize: Sequelize) {} @@ -117,47 +106,6 @@ export class TransactionalSequelizeQueryExecutor extends SequelizeQueryExecutor } } -export class PgPromiseQueryExecutor implements QueryExecutor { - constructor(private readonly db: DbConnection | DbTransaction) {} - - select(query: string, params?: object): Promise { - return this.db.query(formatQuery(query, params)) - } - selectNone(query: string, params?: object): Promise { - return this.db.none(formatQuery(query, params)) - } - selectOneOrNone(query: string, params?: object): Promise { - return this.db.oneOrNone(formatQuery(query, params)) - } - selectOne(query: string, params?: object): Promise { - return this.db.one(formatQuery(query, params)) - } - async result(query: string, params?: object): Promise { - const result = await this.db.result(formatQuery(query, params)) - return result.rowCount - } - - tx(fn: (tx: QueryExecutor) => Promise): Promise { - return this.db.tx((tx) => fn(new PgPromiseQueryExecutor(tx))) - } -} - -export function pgpQx(db: DbConnOrTx): QueryExecutor { - return new PgPromiseQueryExecutor(db) -} - -export function dbStoreQx(dbStore: DbStore): QueryExecutor { - return pgpQx(dbStore.connection()) -} - -export function repoQx(repo: RepositoryBase): QueryExecutor { - return pgpQx(repo.db()) -} - -export function connQx(conn: DbConnOrTx): QueryExecutor { - return pgpQx(conn) -} - export function optionsQx(options: any): QueryExecutor { const seq = options.database.sequelize if (options.transaction) { diff --git a/services/libs/data-access-layer/vitest.config.ts b/services/libs/data-access-layer/vitest.config.ts deleted file mode 100644 index 530cc66032..0000000000 --- a/services/libs/data-access-layer/vitest.config.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defineConfig } from 'vitest/config' - -export default defineConfig({ - test: { - environment: 'node', - include: ['src/**/*.test.ts'], - server: { - deps: { - inline: [/@crowd\//], - }, - }, - }, -}) diff --git a/services/libs/database/src/connection.ts b/services/libs/database/src/connection.ts index 6f1dd2d762..7f0c714bb8 100644 --- a/services/libs/database/src/connection.ts +++ b/services/libs/database/src/connection.ts @@ -87,14 +87,6 @@ export const getDbInstance = (): DbInstance => { return dbInstance } -export const formatQuery = (query: string, values: Record): string => { - if (!dbInstance) { - throw new Error('Database instance not initialized!') - } - - return dbInstance.as.format(query, values) -} - const dbConnection: Record = {} export const getDbConnection = async ( diff --git a/services/libs/database/src/index.ts b/services/libs/database/src/index.ts index 74a7067668..e8faf09cae 100644 --- a/services/libs/database/src/index.ts +++ b/services/libs/database/src/index.ts @@ -1,6 +1,7 @@ export * from './connection' export * from './dbStore' export * from './locking' +export * from './queryExecutor' export * from './repoBase' export * from './tinybirdClient' export * from './types' diff --git a/services/libs/database/src/queryExecutor.ts b/services/libs/database/src/queryExecutor.ts new file mode 100644 index 0000000000..582960534d --- /dev/null +++ b/services/libs/database/src/queryExecutor.ts @@ -0,0 +1,63 @@ +import pgp from 'pg-promise' + +import { DbStore } from './dbStore' +import { RepositoryBase } from './repoBase' +import type { DbConnOrTx, DbConnection, DbTransaction } from './types' + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export interface QueryExecutor { + select(query: string, params?: object): Promise + selectNone(query: string, params?: object): Promise + selectOneOrNone(query: string, params?: object): Promise + selectOne(query: string, params?: object): Promise + result(query: string, params?: object): Promise + + tx(fn: (tx: QueryExecutor) => Promise): Promise +} + +export function formatQuery(query: string, params?: object): string { + return pgp.as.format(query, params) +} + +export class PgPromiseQueryExecutor implements QueryExecutor { + constructor(private readonly db: DbConnection | DbTransaction) {} + + select(query: string, params?: object): Promise { + return this.db.query(formatQuery(query, params)) + } + + selectNone(query: string, params?: object): Promise { + return this.db.none(formatQuery(query, params)) + } + + selectOneOrNone(query: string, params?: object): Promise { + return this.db.oneOrNone(formatQuery(query, params)) + } + + selectOne(query: string, params?: object): Promise { + return this.db.one(formatQuery(query, params)) + } + + async result(query: string, params?: object): Promise { + const result = await this.db.result(formatQuery(query, params)) + return result.rowCount + } + + tx(fn: (tx: QueryExecutor) => Promise): Promise { + return this.db.tx((tx) => fn(new PgPromiseQueryExecutor(tx))) + } +} + +export function pgpQx(db: DbConnOrTx): QueryExecutor { + return new PgPromiseQueryExecutor(db) +} + +export function dbStoreQx(dbStore: DbStore): QueryExecutor { + return pgpQx(dbStore.connection()) +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function repoQx(repo: RepositoryBase): QueryExecutor { + return pgpQx(repo.db()) +} diff --git a/services/libs/test-kit/package.json b/services/libs/test-kit/package.json new file mode 100644 index 0000000000..2ba5fe2ebf --- /dev/null +++ b/services/libs/test-kit/package.json @@ -0,0 +1,23 @@ +{ + "name": "@crowd/test-kit", + "private": true, + "exports": { + "./fixtures": "./src/fixtures.ts" + }, + "scripts": { + "lint": "npx eslint --ext .ts src --max-warnings=0", + "format": "npx prettier --write \"src/**/*.ts\"", + "format-check": "npx prettier --check .", + "tsc-check": "tsc --noEmit" + }, + "dependencies": { + "@crowd/common": "workspace:*", + "@crowd/database": "workspace:*", + "pg-promise": "^11.4.3" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.6.3", + "vitest": "4.1.7" + } +} diff --git a/services/libs/test-kit/src/fixtures.ts b/services/libs/test-kit/src/fixtures.ts new file mode 100644 index 0000000000..f893ec2424 --- /dev/null +++ b/services/libs/test-kit/src/fixtures.ts @@ -0,0 +1,23 @@ +import { test as baseTest } from 'vitest' + +import { pgpQx } from '@crowd/database' + +import { openTestWorkerDatabase, resetTestDatabase, seedTestBaseline } from './postgres' + +export function withQx(test: T) { + return ( + test + // eslint-disable-next-line no-empty-pattern + .extend('_db', { scope: 'file' }, async ({}, { onCleanup }) => { + const worker = await openTestWorkerDatabase() + onCleanup(() => worker.cleanup()) + return worker.db + }) + .extend('qx', async ({ _db }) => { + const executor = pgpQx(_db) + await resetTestDatabase(executor) + await seedTestBaseline(executor) + return executor + }) + ) +} diff --git a/services/libs/test-kit/src/postgres.ts b/services/libs/test-kit/src/postgres.ts new file mode 100644 index 0000000000..c69878b86d --- /dev/null +++ b/services/libs/test-kit/src/postgres.ts @@ -0,0 +1,198 @@ +import pgPromise from 'pg-promise' + +import { DEFAULT_TENANT_ID, IS_TEST_ENV } from '@crowd/common' +import { type DbConnection, type DbInstance, type IDatabaseConfig } from '@crowd/database' +import type { QueryExecutor } from '@crowd/database' + +import type { TestPostgres } from './types' + +/** + * Creates an isolated, dynamic test database cloned from the template database + * for the current Vitest worker pool, and handles its lifecycle cleanup. + */ +export async function openTestWorkerDatabase(): Promise<{ + db: DbConnection + cleanup: () => Promise +}> { + const postgres = getTestPostgres() + + const poolId = process.env.VITEST_POOL_ID ?? '0' + const name = `test_${poolId.replace(/[^a-z0-9_]/gi, '_').toLowerCase()}` + + await withCatalogDb(postgres, async (catalog) => { + await catalog.result(`SELECT pg_advisory_lock(hashtext('cdp-test-template-clone'))`) + try { + await dropDatabase(catalog, name) + await catalog.result( + `SELECT pg_terminate_backend(pid) + FROM pg_stat_activity + WHERE datname = 'test_template' AND pid <> pg_backend_pid()`, + ) + await catalog.none(`CREATE DATABASE ${quoteIdent(name)} WITH TEMPLATE "test_template"`) + } finally { + await catalog.result(`SELECT pg_advisory_unlock(hashtext('cdp-test-template-clone'))`) + } + }) + + const db = connectTestDb({ + host: postgres.host, + port: postgres.port, + user: postgres.user, + password: postgres.password, + database: name, + }) + + return { + db, + async cleanup() { + await db.$pool.end() + await withCatalogDb(postgres, (catalog) => dropDatabase(catalog, name)) + }, + } +} + +/** + * Truncates all public tables in the worker database. + * @throws {Error} If executed against a non-test database name. + */ +export async function resetTestDatabase(qx: QueryExecutor): Promise { + const { name } = await qx.selectOne('SELECT current_database() AS name') + + if (!/^test_[a-z0-9_]+$/.test(name)) { + throw new Error(`Expected worker test database (got ${name})`) + } + + await qx.selectNone(` + DO $$ + DECLARE tables text; + BEGIN + SELECT string_agg(format('%I.%I', schemaname, tablename), ', ') + INTO tables + FROM pg_tables + WHERE schemaname = 'public'; + + IF tables IS NOT NULL THEN + EXECUTE 'TRUNCATE TABLE ' || tables || ' RESTART IDENTITY CASCADE'; + END IF; + END $$; + `) +} + +/** + * Seeds the default tenant into the database after a table reset. + */ +export async function seedTestBaseline(qx: QueryExecutor): Promise { + await qx.result( + ` + INSERT INTO tenants (id, name, url, plan, "createdAt", "updatedAt") + VALUES ($(id), 'Default', 'default', 'Essential', NOW(), NOW()) + ON CONFLICT (id) DO NOTHING + `, + { id: DEFAULT_TENANT_ID }, + ) +} + +/** + * Executes an operation scoped to a short-lived catalog connection (`postgres` DB), + * ensuring the connection pool is cleanly terminated afterward. + */ +async function withCatalogDb( + endpoint: TestPostgres, + fn: (catalog: DbConnection) => Promise, +): Promise { + const catalog = connectTestDb({ + host: endpoint.host, + port: endpoint.port, + user: endpoint.user, + password: endpoint.password, + database: 'postgres', + }) + try { + return await fn(catalog) + } finally { + await catalog.$pool.end() + } +} + +/** + * Forcefully drops a target database by terminating active connections first. + */ +async function dropDatabase(catalog: DbConnection, name: string): Promise { + if (name !== 'test_template' && !/^test_[a-z0-9_]+$/.test(name)) { + throw new Error(`Not a test database: ${name}`) + } + + await catalog.result( + `SELECT pg_terminate_backend(pid) + FROM pg_stat_activity + WHERE datname = $(database) AND pid <> pg_backend_pid()`, + { database: name }, + ) + await catalog.none(`DROP DATABASE IF EXISTS ${quoteIdent(name)}`) +} + +/** + * Resolves and validates local test database credentials from the environment. + */ +function getTestPostgres(): TestPostgres { + if (!IS_TEST_ENV) { + throw new Error(`Expected NODE_ENV=test (got ${process.env.NODE_ENV ?? 'unset'})`) + } + + const { DB_HOST: host, DB_PORT, DB_USER: user, DB_PASSWORD: password } = process.env + + if (!host || !DB_PORT || !user || !password) { + throw new Error('Missing required database environment variables') + } + + const port = Number(DB_PORT) + + if (!Number.isInteger(port) || port <= 0 || port > 65535) { + throw new Error(`Expected valid DB_PORT (got ${DB_PORT})`) + } + + if (!['localhost', '127.0.0.1', '::1'].includes(host)) { + throw new Error(`Expected local DB_HOST (got ${host})`) + } + + return { host, port, user, password } +} + +let testPgInstance: DbInstance | undefined + +/** Test-scoped pg-promise main instance (no prod error/query hooks). */ +function createTestPgPromise(): DbInstance { + if (testPgInstance) { + return testPgInstance + } + + testPgInstance = pgPromise({}) + + // Keep in sync with getDbInstance() + testPgInstance.pg.types.setTypeParser(1114, (s) => s) + testPgInstance.pg.types.setTypeParser(1184, (s) => s) + testPgInstance.pg.types.setTypeParser(1700, (s) => parseFloat(s)) + testPgInstance.pg.types.setTypeParser(23, (s) => parseInt(s, 10)) + + return testPgInstance +} + +function connectTestDb(config: IDatabaseConfig): DbConnection { + return createTestPgPromise()({ + ...config, + ssl: false, + max: 5, + idleTimeoutMillis: 10_000, + application_name: 'cdp-test', + }) +} + +/** + * Standard PostgreSQL identifier wrapping for variable database names. + */ +function quoteIdent(name: string): string { + if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) { + throw new Error(`Invalid database name: ${name}`) + } + return `"${name}"` +} diff --git a/services/libs/test-kit/src/types.ts b/services/libs/test-kit/src/types.ts new file mode 100644 index 0000000000..902271f076 --- /dev/null +++ b/services/libs/test-kit/src/types.ts @@ -0,0 +1,6 @@ +export type TestPostgres = { + host: string + port: number + user: string + password: string +} diff --git a/services/libs/test-kit/tsconfig.json b/services/libs/test-kit/tsconfig.json new file mode 100644 index 0000000000..bf7f183850 --- /dev/null +++ b/services/libs/test-kit/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../base.tsconfig.json", + "include": ["src/**/*"] +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000000..f50903040f --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,49 @@ +import { loadEnv } from 'vite' +import { defineConfig } from 'vitest/config' + +const root = __dirname + +/** Load .env.${mode} and let existing process env vars override file values. */ +function resolveTestEnv(mode: string): Record { + const env = loadEnv(mode, root, '') + const resolved = { ...env } + + for (const key in env) { + const value = process.env[key] + + if (value !== undefined) { + resolved[key] = value + } + } + + return resolved +} + +export default defineConfig(({ mode }) => ({ + test: { + env: resolveTestEnv(mode), + server: { + deps: { inline: [/@crowd\//] }, + }, + projects: [ + { + extends: true, + test: { + name: 'server', + include: ['backend/**/*.test.ts', 'services/**/*.test.ts'], + exclude: [ + '**/node_modules/**', + '**/dist/**', + 'services/cronjobs/**', + // TODO: packages_worker has its own vitest config and packages-db; it was landed in another PR + // alongside the test foundation. excluding this for now, will refactor it later! + 'services/apps/packages_worker/**', + ], + pool: 'forks', + hookTimeout: 300_000, + testTimeout: 30_000, + }, + }, + ], + }, +}))