diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2eed252 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,65 @@ +# Source control and worktrees +.git +.git/** +.worktrees/ + +# Graphify artifacts +.graphify/ +.graphifyignore +graphify-out/ + +# Dependency environments +**/.venv/ +**/venv/ +**/env/ +**/ENV/ +**/node_modules/ + +# Coverage, caches, and generated Python files +**/.coverage +**/.coverage.* +**/coverage/ +**/htmlcov/ +**/.cache/ +**/.npm/ +**/.nox/ +**/.tox/ +**/.mypy_cache/ +**/.pytest_cache/ +**/.ruff_cache/ +**/.vite/ +**/.eslintcache +**/__pycache__/ +**/*.py[cod] + +# Build outputs +**/build/ +**/dist/ +**/out/ +**/.next/ +**/*.egg-info/ +**/wheels/ + +# Secrets and local configuration +**/.env +**/.env.* +!**/.env.example +**/.npmrc +**/.pypirc +**/.secrets/ +**/secrets/ +**/*credentials*.json +**/*.key +**/*.pem +**/id_ed25519* +**/id_rsa* + +# Local application data and logs +/backups/ +/data/ +/backend/data/ +**/*.db +**/*.sqlite +**/*.sqlite3 +**/*.log +**/logs/ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f3cd8bb..ede72c8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -222,6 +222,9 @@ jobs: with: ref: ${{ github.event.workflow_run.head_sha || github.sha }} + - name: Set up QEMU + uses: docker/setup-qemu-action@v4 + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 @@ -236,7 +239,7 @@ jobs: uses: docker/build-push-action@v7 with: context: . - platforms: linux/amd64 + platforms: linux/amd64,linux/arm64 push: true tags: ${{ needs.prepare.outputs.docker_tags }} labels: ${{ needs.prepare.outputs.docker_labels }} diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index ca27366..d4aca04 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -15,7 +15,7 @@ permissions: jobs: # ────────────────────────────────────────────── - # Python SAST — Bandit + # Python SAST - Bandit # ────────────────────────────────────────────── bandit: name: Bandit (Python SAST) @@ -25,20 +25,22 @@ jobs: - uses: actions/setup-python@v6 with: - python-version: '3.14' + python-version-file: '.python-version' - name: Install Bandit - run: pip install bandit[toml] + run: | + python -m pip install --upgrade pip==25.3 + pip install bandit[toml] - name: Run Bandit scan + continue-on-error: true run: | bandit -r backend/app \ -f json \ -o bandit-results.json \ --severity-level medium \ --confidence-level medium \ - -x 'backend/app/__pycache__' \ - || true + -x 'backend/app/__pycache__' - name: Display results if: always() @@ -56,7 +58,7 @@ jobs: retention-days: 30 # ────────────────────────────────────────────── - # Python dependency audit — pip-audit + # Python dependency audit - pip-audit # ────────────────────────────────────────────── pip-audit: name: pip-audit (Python Dependencies) @@ -66,18 +68,20 @@ jobs: - uses: actions/setup-python@v6 with: - python-version: '3.14' + python-version-file: '.python-version' - name: Install pip-audit - run: pip install pip-audit + run: | + python -m pip install --upgrade pip==25.3 + pip install pip-audit - name: Run pip-audit + continue-on-error: true run: | pip-audit \ -r backend/requirements.txt \ --format json \ - --output pip-audit-results.json \ - || true + --output pip-audit-results.json - name: Display results if: always() @@ -95,7 +99,7 @@ jobs: retention-days: 30 # ────────────────────────────────────────────── - # JavaScript dependency audit — npm audit + # JavaScript dependency audit - npm audit # ────────────────────────────────────────────── npm-audit: name: npm audit (JS Dependencies) @@ -105,18 +109,22 @@ jobs: - uses: actions/setup-node@v6 with: - node-version: '24' + node-version-file: '.node-version' cache: 'npm' cache-dependency-path: frontend/package-lock.json + - name: Install pinned npm + working-directory: frontend + run: npm install --global "$(node -p "require('./package.json').packageManager")" + - name: Install dependencies working-directory: frontend run: npm ci - name: Run npm audit working-directory: frontend - run: | - npm audit --json > npm-audit-results.json 2>&1 || true + continue-on-error: true + run: npm audit --json > npm-audit-results.json 2>&1 - name: Display results if: always() @@ -135,7 +143,7 @@ jobs: retention-days: 30 # ────────────────────────────────────────────── - # Container image scanning — Trivy + # Container image scanning - Trivy # ────────────────────────────────────────────── trivy: name: Trivy (Container Scan) @@ -172,7 +180,7 @@ jobs: exit-code: '0' # ────────────────────────────────────────────── - # Dockerfile linting — Hadolint + # Dockerfile linting - Hadolint # ────────────────────────────────────────────── hadolint: name: Hadolint (Dockerfile Lint) @@ -196,7 +204,7 @@ jobs: category: 'hadolint' # ────────────────────────────────────────────── - # Secret scanning — Gitleaks + # Secret scanning - Gitleaks # ────────────────────────────────────────────── gitleaks: name: Gitleaks (Secret Detection) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4eb5c76..b1355d0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,7 +21,7 @@ jobs: - name: Set up Python 3.14 uses: actions/setup-python@v6 with: - python-version: '3.14' + python-version-file: '.python-version' - name: Cache pip dependencies uses: actions/cache@v5 @@ -34,7 +34,7 @@ jobs: - name: Install dependencies working-directory: backend run: | - python -m pip install --upgrade pip + python -m pip install --upgrade pip==25.3 pip install -r requirements.txt pip install -r requirements-dev.txt @@ -66,9 +66,13 @@ jobs: - name: Set up Node.js 24 uses: actions/setup-node@v6 with: - node-version: '24' + node-version-file: '.node-version' cache: 'npm' cache-dependency-path: frontend/package-lock.json + + - name: Install pinned npm + working-directory: frontend + run: npm install --global "$(node -p "require('./package.json').packageManager")" - name: Install dependencies working-directory: frontend @@ -101,21 +105,25 @@ jobs: - name: Set up Python 3.14 uses: actions/setup-python@v6 with: - python-version: '3.14' + python-version-file: '.python-version' - name: Set up Node.js 24 uses: actions/setup-node@v6 with: - node-version: '24' + node-version-file: '.node-version' cache: 'npm' cache-dependency-path: frontend/package-lock.json - name: Install Python dependencies working-directory: backend run: | - python -m pip install --upgrade pip + python -m pip install --upgrade pip==25.3 pip install ruff mypy pip install -r requirements.txt + + - name: Install pinned npm + working-directory: frontend + run: npm install --global "$(node -p "require('./package.json').packageManager")" - name: Install Node dependencies working-directory: frontend @@ -131,7 +139,8 @@ jobs: - name: Type check Python code working-directory: backend - run: mypy app/ || true + run: mypy app/ + continue-on-error: true - name: Lint TypeScript code working-directory: frontend @@ -154,6 +163,21 @@ jobs: - name: Build Docker image run: | docker build -t dockervault:test . + + - name: Check runtime binaries + run: | + docker run --rm --entrypoint /bin/bash dockervault:test -o pipefail -c ' + set -eu + for binary in rclone rsync ssh sftp sshpass age curl; do + command -v "$binary" + done + rclone version | grep -F "rclone v1.72.1" + rsync --version + ssh -V + sshpass -V + age --version + curl --version + ' - name: Run integration tests run: | @@ -163,16 +187,25 @@ jobs: -v /var/run/docker.sock:/var/run/docker.sock:ro \ dockervault:test - # Wait for container to be healthy with retries + # Wait for backend liveness through nginx with retries echo "Waiting for container to be ready..." + ready=false for i in {1..30}; do - if curl -sf http://localhost:8080 > /dev/null 2>&1; then - echo "Frontend is ready!" + if health=$(curl -fsS http://localhost:8080/health 2>/dev/null) && \ + [[ "$health" == *'"status":"healthy"'* ]]; then + echo "Backend is healthy through nginx!" + ready=true break fi echo "Attempt $i/30 - waiting..." sleep 2 done + + if [ "$ready" != true ]; then + echo "Backend did not become healthy through nginx" + docker logs dockervault-test + exit 1 + fi # Show container logs for debugging echo "=== Container logs ===" @@ -181,16 +214,12 @@ jobs: # Test frontend serves correctly curl -f http://localhost:8080 || exit 1 - + # Test backend health directly (internal port) - docker exec dockervault-test curl -sf http://localhost:8000/health || echo "Backend health check not available" - - # Test API through frontend proxy - curl -f http://localhost:8080/api/v1/docker/health || echo "API health check failed (may be expected without Docker socket)" - - # Clean up - docker stop dockervault-test - docker rm dockervault-test + docker exec dockervault-test curl -fsS http://localhost:8000/health + + # Test a public API endpoint through the nginx proxy + curl -fsS http://localhost:8080/api/v1/auth/status - name: Clean up if: always() @@ -211,7 +240,7 @@ jobs: - name: Set up Python 3.14 uses: actions/setup-python@v6 with: - python-version: '3.14' + python-version-file: '.python-version' - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@0.35.0 @@ -231,12 +260,24 @@ jobs: - name: Run pip-audit on Python dependencies working-directory: backend + continue-on-error: true run: | + python -m pip install --upgrade pip==25.3 pip install pip-audit - pip-audit -r requirements.txt || true + pip-audit -r requirements.txt + + - name: Set up Node.js 24 + uses: actions/setup-node@v6 + with: + node-version-file: '.node-version' + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + + - name: Install pinned npm + working-directory: frontend + run: npm install --global "$(node -p "require('./package.json').packageManager")" - name: Run npm audit on Node dependencies working-directory: frontend - run: | - npm audit --audit-level high || true - + continue-on-error: true + run: npm audit --audit-level high diff --git a/.node-version b/.node-version new file mode 100644 index 0000000..f88da62 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +24.11 diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..6324d40 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.14 diff --git a/Dockerfile b/Dockerfile index fd3173d..d6318aa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,15 +4,16 @@ # ============================================================================= # Stage 1: Frontend Dependencies # ============================================================================= -FROM node:24-alpine AS frontend-deps +FROM node:24.11-alpine AS frontend-deps WORKDIR /app # Copy only package files for dependency caching COPY frontend/package.json frontend/package-lock.json* ./ -# Install dependencies (cached if package files unchanged) -RUN npm ci --prefer-offline --no-audit +# Install the package-manager version declared by the project, then dependencies +RUN npm install --global npm@11.6.2 && \ + npm ci --prefer-offline --no-audit # ============================================================================= # Stage 2: Build Frontend @@ -99,6 +100,27 @@ RUN ARCH=$(dpkg --print-architecture) && \ tar -xz -C /usr/local/bin --strip-components=1 age/age age/age-keygen && \ chmod +x /usr/local/bin/age /usr/local/bin/age-keygen +# Install rclone from a pinned release using checksums published by rclone.org +ARG RCLONE_VERSION=1.72.1 +RUN ARCH=$(dpkg --print-architecture) && \ + if [ "$ARCH" = "amd64" ]; then \ + RCLONE_ARCH="amd64"; \ + RCLONE_SHA256="b5c9b2fb6ada8a400c5fc5d48cd112dc1adea21a3b73b03857059374dd8a78d0"; \ + elif [ "$ARCH" = "arm64" ]; then \ + RCLONE_ARCH="arm64"; \ + RCLONE_SHA256="66ce9c7fbdf6ba38991fa2ac193ed051bd6d04aeec693900c848154bf549484f"; \ + else \ + echo "Unsupported architecture: $ARCH" && exit 1; \ + fi && \ + RCLONE_ARCHIVE="rclone-v${RCLONE_VERSION}-linux-${RCLONE_ARCH}.zip" && \ + curl -fsSLo "/tmp/${RCLONE_ARCHIVE}" \ + "https://downloads.rclone.org/v${RCLONE_VERSION}/${RCLONE_ARCHIVE}" && \ + echo "${RCLONE_SHA256} /tmp/${RCLONE_ARCHIVE}" | sha256sum --check - && \ + python -m zipfile -e "/tmp/${RCLONE_ARCHIVE}" /tmp/rclone && \ + install -m 0755 "/tmp/rclone/rclone-v${RCLONE_VERSION}-linux-${RCLONE_ARCH}/rclone" \ + /usr/local/bin/rclone && \ + rm -rf "/tmp/${RCLONE_ARCHIVE}" /tmp/rclone + # Copy Python virtual environment from builder COPY --from=python-deps /opt/venv /opt/venv ENV PATH="/opt/venv/bin:$PATH" \ @@ -157,11 +179,10 @@ EXPOSE 80 # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \ - CMD curl -sf http://localhost:8000/health || exit 1 + CMD curl -sf http://localhost/health || exit 1 # Declare volumes VOLUME ["/app/data", "/backups"] # Entrypoint handles docker group setup and starts supervisord ENTRYPOINT ["/entrypoint.sh"] - diff --git a/backend/tests/test_dockerfile_npm_version.py b/backend/tests/test_dockerfile_npm_version.py new file mode 100644 index 0000000..7080b8c --- /dev/null +++ b/backend/tests/test_dockerfile_npm_version.py @@ -0,0 +1,18 @@ +import json +import re +from pathlib import Path + + +def test_dockerfile_npm_version_matches_package_manager(): + repository = Path(__file__).resolve().parents[2] + dockerfile = (repository / "Dockerfile").read_text() + package = json.loads((repository / "frontend/package.json").read_text()) + + npm_install = re.search( + r"^RUN npm install --global (npm@\d+\.\d+\.\d+)(?:\s|$)", + dockerfile, + re.MULTILINE, + ) + + assert npm_install is not None, "Dockerfile must install a literal npm@version" + assert npm_install.group(1) == package["packageManager"] diff --git a/docker-compose.yml b/docker-compose.yml index b711462..220d7ee 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -55,7 +55,7 @@ services: networks: - backup-network healthcheck: - test: ["CMD", "curl", "-sf", "http://localhost:8000/health"] + test: ["CMD", "curl", "-sf", "http://localhost/health"] interval: 30s timeout: 10s retries: 3 diff --git a/frontend/nginx.conf b/frontend/nginx.conf index d1b35de..78431af 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -17,6 +17,16 @@ server { gzip_proxied expired no-cache no-store private auth; gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml application/javascript; + # Backend liveness probe, kept outside the SPA fallback + location = /health { + proxy_pass http://127.0.0.1:8000/health; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + # API proxy location /api { proxy_pass http://127.0.0.1:8000; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5d0cc9e..0c5f5fa 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -23,7 +23,7 @@ "zustand": "^5.0.12" }, "devDependencies": { - "@eslint/js": "^10.0.1", + "@eslint/js": "^9.39.2", "@tailwindcss/postcss": "^4.2.2", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", @@ -45,6 +45,10 @@ "typescript-eslint": "^8.57.0", "vite": "^8.0.8", "vitest": "^4.1.4" + }, + "engines": { + "node": ">=24.11 <25", + "npm": "11.6.2" } }, "node_modules/@adobe/css-tools": { @@ -149,6 +153,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -479,6 +484,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=20.19.0" }, @@ -527,37 +533,15 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=20.19.0" } }, - "node_modules/@emnapi/core": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", - "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", - "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", "dev": true, "license": "MIT", "optional": true, @@ -686,24 +670,16 @@ } }, "node_modules/@eslint/js": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", - "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", "dev": true, "license": "MIT", "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "eslint": "^10.0.0" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } } }, "node_modules/@eslint/object-schema": { @@ -1281,6 +1257,40 @@ "node": ">=14.0.0" } }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@rolldown/binding-win32-arm64-msvc": { "version": "1.0.0-rc.15", "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", @@ -1801,8 +1811,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/chai": { "version": "5.2.3", @@ -1905,6 +1914,7 @@ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1915,6 +1925,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -1977,6 +1988,7 @@ "integrity": "sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.58.1", "@typescript-eslint/types": "8.58.1", @@ -2259,6 +2271,7 @@ "integrity": "sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@bcoe/v8-coverage": "^1.0.2", "@vitest/utils": "4.1.4", @@ -2403,6 +2416,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2624,6 +2638,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -2860,7 +2875,8 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/d3-array": { "version": "3.2.4", @@ -3079,8 +3095,7 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dunder-proto": { "version": "1.0.1", @@ -3228,6 +3243,7 @@ "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4470,7 +4486,6 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -4615,6 +4630,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@inquirer/confirm": "^5.0.0", "@mswjs/interceptors": "^0.41.2", @@ -4837,6 +4853,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -4864,6 +4881,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -4896,7 +4914,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -4912,7 +4929,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -4925,8 +4941,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/proxy-from-env": { "version": "2.1.0", @@ -4952,6 +4967,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -4961,6 +4977,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -4997,6 +5014,7 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "license": "MIT", + "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -5101,7 +5119,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -5583,6 +5602,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -5713,6 +5733,7 @@ "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -6052,6 +6073,7 @@ "integrity": "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.1.4", "@vitest/mocker": "4.1.4", @@ -6337,6 +6359,7 @@ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/frontend/package.json b/frontend/package.json index 17595a4..4867018 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -3,6 +3,11 @@ "private": true, "version": "1.0.0", "type": "module", + "packageManager": "npm@11.6.2", + "engines": { + "node": ">=24.11 <25", + "npm": "11.6.2" + }, "scripts": { "dev": "vite", "build": "tsc && vite build", @@ -28,7 +33,7 @@ "zustand": "^5.0.12" }, "devDependencies": { - "@eslint/js": "^10.0.1", + "@eslint/js": "^9.39.2", "@tailwindcss/postcss": "^4.2.2", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2",