diff --git a/.github/scripts/generate-audit-summary-npm.sh b/.github/scripts/generate-audit-summary-npm.sh new file mode 100755 index 0000000..08f538a --- /dev/null +++ b/.github/scripts/generate-audit-summary-npm.sh @@ -0,0 +1,82 @@ +#!/bin/bash +set -e + +# Generate Security Audit Summary from npm audit JSON output +# Usage: ./generate-audit-summary-npm.sh +# Example: ./generate-audit-summary-npm.sh audit-results.json "TanStack App" + +# Guard: skip if GITHUB_STEP_SUMMARY is not set +if [ -z "$GITHUB_STEP_SUMMARY" ]; then + echo "Warning: GITHUB_STEP_SUMMARY not set, skipping summary generation" + exit 0 +fi + +AUDIT_JSON="${1:-}" +PROJECT_LABEL="${2:-npm project}" + +if [ ! -f "$AUDIT_JSON" ]; then + echo "âš ī¸ Audit JSON file not found: $AUDIT_JSON" >> "$GITHUB_STEP_SUMMARY" + exit 1 +fi + +echo "## 🔒 Security Audit — $PROJECT_LABEL" >> "$GITHUB_STEP_SUMMARY" +echo "" >> "$GITHUB_STEP_SUMMARY" + +# Extract vulnerability counts from metadata +info=$(jq -r '.metadata.vulnerabilities.info // 0' "$AUDIT_JSON") +low=$(jq -r '.metadata.vulnerabilities.low // 0' "$AUDIT_JSON") +moderate=$(jq -r '.metadata.vulnerabilities.moderate // 0' "$AUDIT_JSON") +high=$(jq -r '.metadata.vulnerabilities.high // 0' "$AUDIT_JSON") +critical=$(jq -r '.metadata.vulnerabilities.critical // 0' "$AUDIT_JSON") +total=$(jq -r '.metadata.vulnerabilities.total // 0' "$AUDIT_JSON") + +# Summary table +echo "### Vulnerability Summary" >> "$GITHUB_STEP_SUMMARY" +echo "" >> "$GITHUB_STEP_SUMMARY" +echo "| Severity | Count |" >> "$GITHUB_STEP_SUMMARY" +echo "|----------|-------|" >> "$GITHUB_STEP_SUMMARY" +[ "$critical" -gt 0 ] && echo "| 🔴 Critical | $critical |" >> "$GITHUB_STEP_SUMMARY" +[ "$high" -gt 0 ] && echo "| 🟠 High | $high |" >> "$GITHUB_STEP_SUMMARY" +[ "$moderate" -gt 0 ] && echo "| 🟡 Moderate | $moderate |" >> "$GITHUB_STEP_SUMMARY" +[ "$low" -gt 0 ] && echo "| đŸ”ĩ Low | $low |" >> "$GITHUB_STEP_SUMMARY" +[ "$info" -gt 0 ] && echo "| â„šī¸ Info | $info |" >> "$GITHUB_STEP_SUMMARY" +echo "| **Total** | **$total** |" >> "$GITHUB_STEP_SUMMARY" +echo "" >> "$GITHUB_STEP_SUMMARY" + +# If there are vulnerabilities, list them +if [ "$total" -gt 0 ]; then + echo "### Vulnerability Details" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "| Package | Severity | Fix Available | Details |" >> "$GITHUB_STEP_SUMMARY" + echo "|---------|----------|---------------|---------|" >> "$GITHUB_STEP_SUMMARY" + + # Extract each vulnerability with a direct advisory (not just transitive references) + jq -r ' + .vulnerabilities | to_entries[] | + .value | + select(.via | map(type) | any(. == "object")) | + { + name: .name, + severity: .severity, + fix: (if .fixAvailable == true then "✅ Yes" + elif .fixAvailable == false then "❌ No" + elif .fixAvailable != null then "✅ \(.fixAvailable.name)@\(.fixAvailable.version)" + else "❓ Unknown" end), + title: ([.via[] | select(type == "object") | .title] | first // "N/A") + } | + "| \(.name) | \(.severity) | \(.fix) | \(.title) |" + ' "$AUDIT_JSON" >> "$GITHUB_STEP_SUMMARY" 2>/dev/null || true + + echo "" >> "$GITHUB_STEP_SUMMARY" + + # Action items + echo "### đŸ› ī¸ What to do" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "1. Run \`npm audit\` locally to see full details" >> "$GITHUB_STEP_SUMMARY" + echo "2. Run \`npm audit fix\` to auto-fix where possible" >> "$GITHUB_STEP_SUMMARY" + echo "3. For breaking fixes: \`npm audit fix --force\` (review changes carefully)" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "❌ **Audit failed — vulnerabilities found!**" >> "$GITHUB_STEP_SUMMARY" +else + echo "✅ **No vulnerabilities found!**" >> "$GITHUB_STEP_SUMMARY" +fi diff --git a/.github/scripts/generate-audit-summary-pip.sh b/.github/scripts/generate-audit-summary-pip.sh new file mode 100755 index 0000000..25e6b38 --- /dev/null +++ b/.github/scripts/generate-audit-summary-pip.sh @@ -0,0 +1,68 @@ +#!/bin/bash +set -e + +# Generate Security Audit Summary from pip-audit JSON output +# Usage: ./generate-audit-summary-pip.sh +# Example: ./generate-audit-summary-pip.sh audit-results.json "Python FastAPI" + +# Guard: skip if GITHUB_STEP_SUMMARY is not set +if [ -z "$GITHUB_STEP_SUMMARY" ]; then + echo "Warning: GITHUB_STEP_SUMMARY not set, skipping summary generation" + exit 0 +fi + +AUDIT_JSON="${1:-}" +PROJECT_LABEL="${2:-Python project}" + +if [ ! -f "$AUDIT_JSON" ]; then + echo "âš ī¸ Audit JSON file not found: $AUDIT_JSON" >> "$GITHUB_STEP_SUMMARY" + exit 1 +fi + +echo "## 🔒 Security Audit — $PROJECT_LABEL" >> "$GITHUB_STEP_SUMMARY" +echo "" >> "$GITHUB_STEP_SUMMARY" + +# pip-audit JSON structure: { "dependencies": [ { "name": "...", "version": "...", "vulns": [...] } ] } +# Count vulnerable packages (those with non-empty vulns arrays) +total_deps=$(jq '[.dependencies | length] | first // 0' "$AUDIT_JSON") +vuln_packages=$(jq '[.dependencies[] | select(.vulns | length > 0)] | length' "$AUDIT_JSON") +total_vulns=$(jq '[.dependencies[].vulns | length] | add // 0' "$AUDIT_JSON") + +# Summary table +echo "### Vulnerability Summary" >> "$GITHUB_STEP_SUMMARY" +echo "" >> "$GITHUB_STEP_SUMMARY" +echo "| Metric | Count |" >> "$GITHUB_STEP_SUMMARY" +echo "|--------|-------|" >> "$GITHUB_STEP_SUMMARY" +echo "| đŸ“Ļ Total dependencies scanned | $total_deps |" >> "$GITHUB_STEP_SUMMARY" +echo "| âš ī¸ Vulnerable packages | $vuln_packages |" >> "$GITHUB_STEP_SUMMARY" +echo "| 🔓 Total vulnerabilities | $total_vulns |" >> "$GITHUB_STEP_SUMMARY" +echo "" >> "$GITHUB_STEP_SUMMARY" + +# If there are vulnerabilities, list them +if [ "$total_vulns" -gt 0 ]; then + echo "### Vulnerability Details" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "| Package | Version | Vulnerability ID | Fix Versions |" >> "$GITHUB_STEP_SUMMARY" + echo "|---------|---------|-----------------|--------------|" >> "$GITHUB_STEP_SUMMARY" + + jq -r ' + .dependencies[] | + select(.vulns | length > 0) | + . as $dep | + .vulns[] | + "| \($dep.name) | \($dep.version) | \(.id) | \(.fix_versions | join(", ") // "N/A") |" + ' "$AUDIT_JSON" >> "$GITHUB_STEP_SUMMARY" 2>/dev/null || true + + echo "" >> "$GITHUB_STEP_SUMMARY" + + # Action items + echo "### đŸ› ī¸ What to do" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "1. Update the affected packages in \`requirements.in\`" >> "$GITHUB_STEP_SUMMARY" + echo "2. Run \`pip-compile requirements.in\` to regenerate \`requirements.txt\`" >> "$GITHUB_STEP_SUMMARY" + echo "3. Run \`pip-audit -r requirements.txt\` locally to verify the fix" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "❌ **Audit failed — vulnerabilities found!**" >> "$GITHUB_STEP_SUMMARY" +else + echo "✅ **No vulnerabilities found!**" >> "$GITHUB_STEP_SUMMARY" +fi diff --git a/.github/workflows/Security-Notification.yml b/.github/workflows/Security-Notification.yml index c532741..a820c20 100644 --- a/.github/workflows/Security-Notification.yml +++ b/.github/workflows/Security-Notification.yml @@ -7,6 +7,9 @@ on: # Allows you to test manually workflow_dispatch: +permissions: + contents: read + jobs: check-alerts: runs-on: ubuntu-latest diff --git a/.github/workflows/audit-python-fastapi.yml b/.github/workflows/audit-python-fastapi.yml new file mode 100644 index 0000000..fc3ab7b --- /dev/null +++ b/.github/workflows/audit-python-fastapi.yml @@ -0,0 +1,49 @@ +name: Audit Python FastAPI Dependencies + +on: + pull_request: + branches: + - development + paths: + - 'mflix/server/python-fastapi/**' + +permissions: + contents: read + +jobs: + audit: + name: pip-audit (Python FastAPI) + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + + - name: Install pip-audit + run: pip install pip-audit + + - name: Audit dependencies + working-directory: mflix/server/python-fastapi + run: pip-audit -r requirements.txt --format json -o audit-results.json || true + + - name: Generate audit summary + if: always() + run: | + chmod +x .github/scripts/generate-audit-summary-pip.sh + .github/scripts/generate-audit-summary-pip.sh \ + mflix/server/python-fastapi/audit-results.json \ + "Python FastAPI" + + - name: Check for vulnerabilities + run: | + vuln_count=$(jq '[.dependencies[].vulns | length] | add // 0' mflix/server/python-fastapi/audit-results.json) + if [ "$vuln_count" -gt 0 ]; then + echo "❌ Found $vuln_count vulnerabilities" + exit 1 + fi + echo "✅ No vulnerabilities found" diff --git a/.github/workflows/audit-tanstack.yml b/.github/workflows/audit-tanstack.yml new file mode 100644 index 0000000..60903ed --- /dev/null +++ b/.github/workflows/audit-tanstack.yml @@ -0,0 +1,69 @@ +name: Audit TanStack Dependencies + +on: + pull_request: + branches: + - development + - frameworks-tanstack + paths: + - 'frameworks/javascript/tanstack/**' + +permissions: + contents: read + +jobs: + audit: + name: npm audit (TanStack) + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies (app) + working-directory: frameworks/javascript/tanstack/app + run: npm install + + - name: Audit app dependencies + working-directory: frameworks/javascript/tanstack/app + run: npm audit --json > audit-results-app.json || true + + - name: Install dependencies (bluehawk) + working-directory: frameworks/javascript/tanstack + run: npm install + + - name: Audit bluehawk dependencies + working-directory: frameworks/javascript/tanstack + run: npm audit --json > audit-results-bluehawk.json || true + + - name: Generate audit summary (app) + if: always() + run: | + chmod +x .github/scripts/generate-audit-summary-npm.sh + .github/scripts/generate-audit-summary-npm.sh \ + frameworks/javascript/tanstack/app/audit-results-app.json \ + "TanStack App" + + - name: Generate audit summary (bluehawk) + if: always() + run: | + chmod +x .github/scripts/generate-audit-summary-npm.sh + .github/scripts/generate-audit-summary-npm.sh \ + frameworks/javascript/tanstack/audit-results-bluehawk.json \ + "TanStack Bluehawk" + + - name: Check for high+ vulnerabilities + run: | + app_high=$(jq -r '[(.metadata.vulnerabilities.high // 0), (.metadata.vulnerabilities.critical // 0)] | add' frameworks/javascript/tanstack/app/audit-results-app.json) + bh_high=$(jq -r '[(.metadata.vulnerabilities.high // 0), (.metadata.vulnerabilities.critical // 0)] | add' frameworks/javascript/tanstack/audit-results-bluehawk.json) + total=$((app_high + bh_high)) + if [ "$total" -gt 0 ]; then + echo "❌ Found $total high/critical vulnerabilities" + exit 1 + fi + echo "✅ No high/critical vulnerabilities found" diff --git a/.github/workflows/new-issue-notify.yml b/.github/workflows/new-issue-notify.yml index c4776fe..61620cd 100644 --- a/.github/workflows/new-issue-notify.yml +++ b/.github/workflows/new-issue-notify.yml @@ -5,6 +5,7 @@ on: issues: types: [opened] +permissions: {} jobs: notify_slack_on_issue: diff --git a/.github/workflows/run-express-tests.yml b/.github/workflows/run-express-tests.yml index 5f2fdd4..2fe93b5 100644 --- a/.github/workflows/run-express-tests.yml +++ b/.github/workflows/run-express-tests.yml @@ -12,6 +12,9 @@ on: paths: - 'mflix/server/js-express/**' +permissions: + contents: read + jobs: test: name: Run Express Tests diff --git a/.github/workflows/run-java-spring-boot-tests.yml b/.github/workflows/run-java-spring-boot-tests.yml index 2709569..d319e6b 100644 --- a/.github/workflows/run-java-spring-boot-tests.yml +++ b/.github/workflows/run-java-spring-boot-tests.yml @@ -12,6 +12,9 @@ on: paths: - 'mflix/server/java-spring/**' +permissions: + contents: read + jobs: test: name: Run Java Spring Boot Tests diff --git a/.github/workflows/run-python-tests.yml b/.github/workflows/run-python-tests.yml index 4568fa9..be9b554 100644 --- a/.github/workflows/run-python-tests.yml +++ b/.github/workflows/run-python-tests.yml @@ -12,6 +12,9 @@ on: paths: - 'mflix/server/python-fastapi/**' +permissions: + contents: read + jobs: test: name: Run Python Tests diff --git a/.github/workflows/run-tanstack-tests.yml b/.github/workflows/run-tanstack-tests.yml index 8685980..87c40fe 100644 --- a/.github/workflows/run-tanstack-tests.yml +++ b/.github/workflows/run-tanstack-tests.yml @@ -14,6 +14,9 @@ on: paths: - 'frameworks/javascript/tanstack/**' +permissions: + contents: read + jobs: test: name: Run TanStack Tests diff --git a/.gitignore b/.gitignore index e90af98..6a94f0a 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ Thumbs.db logs/ *.log + # Temporary Files (Global) *.tmp *.temp @@ -52,3 +53,9 @@ yarn-error.log* coverage/ *.lcov .nyc_output + +# Security Audit Results (Generated by CI/local testing) +audit-results*.json + +# Security Audit Results (Generated by local testing) +audit-results*.json diff --git a/README.md b/README.md index 809170c..368fd9f 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,28 @@ Framework examples follow a simpler workflow: No copier tool is used - snippets are committed directly to this repository. +### Security Audits + +Dependency security audits run automatically on PRs to `development`: + +- **`audit-tanstack.yml`** — runs `npm audit` on TanStack app and Bluehawk dependencies +- **`audit-python-fastapi.yml`** — runs `pip-audit` on Python FastAPI dependencies + +**To test locally:** + +```bash +# npm (TanStack) +cd frameworks/javascript/tanstack/app +npm audit --audit-level=high + +# pip (Python FastAPI) +cd mflix/server/python-fastapi +pip install pip-audit +pip-audit -r requirements.txt +``` + +If an audit fails on your PR, check the workflow summary for details on which packages need updating. + ### MFlix Release Process When you merge a release PR from `development` to `main`, the copier tool diff --git a/frameworks/javascript/tanstack/app/package-lock.json b/frameworks/javascript/tanstack/app/package-lock.json index 81d5b47..d586122 100644 --- a/frameworks/javascript/tanstack/app/package-lock.json +++ b/frameworks/javascript/tanstack/app/package-lock.json @@ -9,11 +9,11 @@ "@tailwindcss/vite": "4.2.2", "@tanstack/react-devtools": "0.10.0", "@tanstack/react-query": "5.95.2", - "@tanstack/react-router": "1.168.7", - "@tanstack/react-router-devtools": "1.166.11", - "@tanstack/react-router-ssr-query": "1.166.10", - "@tanstack/react-start": "1.167.12", - "@tanstack/router-plugin": "1.167.8", + "@tanstack/react-router": "1.170.8", + "@tanstack/react-router-devtools": "1.167.0", + "@tanstack/react-router-ssr-query": "1.167.0", + "@tanstack/react-start": "1.168.13", + "@tanstack/router-plugin": "1.168.11", "lucide-react": "0.545.0", "mongodb": "7.1.1", "react": "19.2.4", @@ -32,9 +32,9 @@ "dotenv": "17.4.2", "jsdom": "28.1.0", "typescript": "5.9.3", - "vite": "7.3.1", + "vite": "7.3.2", "vite-tsconfig-paths": "5.1.4", - "vitest": "3.2.4" + "vitest": "4.1.8" } }, "node_modules/@acemir/cssom": { @@ -227,9 +227,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -291,12 +291,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", - "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -306,12 +306,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", - "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1097,9 +1097,9 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.40", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.40.tgz", - "integrity": "sha512-s3GeJKSQOwBlzdUrj4ISjJj5SfSh+aqn0wjOar4Bx95iV1ETI7F6S/5hLcfAxZ9kXDcyrAkxPlqmd1ZITttf+w==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { @@ -1187,6 +1187,9 @@ "cpu": [ "arm" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1200,6 +1203,9 @@ "cpu": [ "arm" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1213,6 +1219,9 @@ "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1226,6 +1235,9 @@ "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1239,6 +1251,9 @@ "cpu": [ "loong64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1252,6 +1267,9 @@ "cpu": [ "loong64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1265,6 +1283,9 @@ "cpu": [ "ppc64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1278,6 +1299,9 @@ "cpu": [ "ppc64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1291,6 +1315,9 @@ "cpu": [ "riscv64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1304,6 +1331,9 @@ "cpu": [ "riscv64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1317,6 +1347,9 @@ "cpu": [ "s390x" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1330,6 +1363,9 @@ "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1343,6 +1379,9 @@ "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1501,6 +1540,13 @@ "solid-js": "^1.6.12" } }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, "node_modules/@tailwindcss/node": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz", @@ -1626,6 +1672,9 @@ "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1642,6 +1691,9 @@ "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1658,6 +1710,9 @@ "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1674,6 +1729,9 @@ "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1712,64 +1770,6 @@ "node": ">=14.0.0" } }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.8.1", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.8.1", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.1", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1", - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { - "version": "2.8.1", - "inBundle": true, - "license": "0BSD", - "optional": true - }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz", @@ -1962,9 +1962,9 @@ } }, "node_modules/@tanstack/history": { - "version": "1.161.6", - "resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.161.6.tgz", - "integrity": "sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg==", + "version": "1.162.0", + "resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.162.0.tgz", + "integrity": "sha512-79pf/RkhteYZTRgcR4F9kbk84P2N8rugQJswxfIqovlbRiT3yI7eBE+5QorIrZaOKktsgzRlXh1l/du/xpl4iA==", "license": "MIT", "engines": { "node": ">=20.19" @@ -2023,14 +2023,14 @@ } }, "node_modules/@tanstack/react-router": { - "version": "1.168.7", - "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.168.7.tgz", - "integrity": "sha512-fW/HvQja4PQeu9lsoyh+pXpZ0UXezbpQkkJvCuH6tHAaW3jvPkjh14lfadrNNiY+pXT7WiMTB3afGhTCC78PDQ==", + "version": "1.170.8", + "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.170.8.tgz", + "integrity": "sha512-Qw2ju6jjnIsMpuW+VrnHZWHuugqs592PWsnI56sG28qNhg14CgRLahOcNajfuJR9P4MxKGP94WVzmFKSYUz/ig==", "license": "MIT", "dependencies": { - "@tanstack/history": "1.161.6", + "@tanstack/history": "1.162.0", "@tanstack/react-store": "^0.9.3", - "@tanstack/router-core": "1.168.6", + "@tanstack/router-core": "1.171.6", "isbot": "^5.1.22" }, "engines": { @@ -2046,12 +2046,12 @@ } }, "node_modules/@tanstack/react-router-devtools": { - "version": "1.166.11", - "resolved": "https://registry.npmjs.org/@tanstack/react-router-devtools/-/react-router-devtools-1.166.11.tgz", - "integrity": "sha512-WYR3q4Xui5yPT/5PXtQh8i03iUA7q8dONBjWpV3nsGdM8Cs1FxpfhLstW0wZO1dOvSyElscwTRCJ6nO5N8r3Lg==", + "version": "1.167.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-router-devtools/-/react-router-devtools-1.167.0.tgz", + "integrity": "sha512-nGw095EG7IHx0h5NtlEmzf6vcCTaFNPWdTSuDKazajhN0ct/v/TkekJ9J6KYUCeV1a8/2ZmToc58M+0rrOyn7w==", "license": "MIT", "dependencies": { - "@tanstack/router-devtools-core": "1.167.1" + "@tanstack/router-devtools-core": "1.168.0" }, "engines": { "node": ">=20.19" @@ -2061,8 +2061,8 @@ "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "@tanstack/react-router": "^1.168.2", - "@tanstack/router-core": "^1.168.2", + "@tanstack/react-router": "^1.170.0", + "@tanstack/router-core": "^1.170.0", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" }, @@ -2073,12 +2073,12 @@ } }, "node_modules/@tanstack/react-router-ssr-query": { - "version": "1.166.10", - "resolved": "https://registry.npmjs.org/@tanstack/react-router-ssr-query/-/react-router-ssr-query-1.166.10.tgz", - "integrity": "sha512-Ny5jKZPSy+RBXICJBJkW2q3SKjEwVooIn2zuWfIFL1MNVImQPh/p+yvqDqKdJseIQ45B4JsqFtWVcdy/6rQ0Rg==", + "version": "1.167.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-router-ssr-query/-/react-router-ssr-query-1.167.0.tgz", + "integrity": "sha512-lJC/qySnlB0RaPCwCS4BQbdsDwyaPP2C8tzuEUsrwPTxm8TVYonYv3sptoSVhY0C2f8i5041X8gbRL7+lSY8BQ==", "license": "MIT", "dependencies": { - "@tanstack/router-ssr-query-core": "1.167.0" + "@tanstack/router-ssr-query-core": "1.169.0" }, "engines": { "node": ">=20.19" @@ -2096,23 +2096,21 @@ } }, "node_modules/@tanstack/react-start": { - "version": "1.167.12", - "resolved": "https://registry.npmjs.org/@tanstack/react-start/-/react-start-1.167.12.tgz", - "integrity": "sha512-My4ICbFKlpkA5Uxk/7kczXKLHtBaxNzUzrPSXlLcRz87UhMAUNw92ujIGEUhYs3BGuajjdlAvhH4HW8wIkWhkw==", - "license": "MIT", - "dependencies": { - "@tanstack/react-router": "1.168.7", - "@tanstack/react-start-client": "1.166.22", - "@tanstack/react-start-server": "1.166.22", - "@tanstack/router-utils": "^1.161.6", - "@tanstack/start-client-core": "1.167.6", - "@tanstack/start-plugin-core": "1.167.13", - "@tanstack/start-server-core": "1.167.6", + "version": "1.168.13", + "resolved": "https://registry.npmjs.org/@tanstack/react-start/-/react-start-1.168.13.tgz", + "integrity": "sha512-E2pHQ92NiND1/HiD5Ax71xFXxiRZ2reOfU5W4BqxUL5plap3p8xSw1c6L8Np1E60vsxknuPCYRZESKkRy/LkOA==", + "license": "MIT", + "dependencies": { + "@tanstack/react-router": "1.170.8", + "@tanstack/react-start-client": "1.168.4", + "@tanstack/react-start-rsc": "0.1.13", + "@tanstack/react-start-server": "1.167.9", + "@tanstack/router-utils": "1.162.1", + "@tanstack/start-client-core": "1.170.4", + "@tanstack/start-plugin-core": "1.171.6", + "@tanstack/start-server-core": "1.169.4", "pathe": "^2.0.3" }, - "bin": { - "intent": "bin/intent.js" - }, "engines": { "node": ">=22.12.0" }, @@ -2121,20 +2119,32 @@ "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { + "@rsbuild/core": "^2.0.0", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0", "vite": ">=7.0.0" + }, + "peerDependenciesMeta": { + "@rsbuild/core": { + "optional": true + }, + "@vitejs/plugin-rsc": { + "optional": true + }, + "vite": { + "optional": true + } } }, "node_modules/@tanstack/react-start-client": { - "version": "1.166.22", - "resolved": "https://registry.npmjs.org/@tanstack/react-start-client/-/react-start-client-1.166.22.tgz", - "integrity": "sha512-q8GjmehiKPlmbZ2ZjIjeTR8u8Xr8kilbD/AIAvBpd5GCHvwSKYSjgQXSQXbe+B8wqiGXrrid7R9DAeurDHM46A==", + "version": "1.168.4", + "resolved": "https://registry.npmjs.org/@tanstack/react-start-client/-/react-start-client-1.168.4.tgz", + "integrity": "sha512-PDJ7xEuUKrlBiQz2PrVN9pD2ErmWeFpckYW1WUE8JCAeVi8U7C6rQNTQe4hQxBhycRfRdD53M6UfdWdQODIxyg==", "license": "MIT", "dependencies": { - "@tanstack/react-router": "1.168.7", - "@tanstack/router-core": "1.168.6", - "@tanstack/start-client-core": "1.167.6" + "@tanstack/react-router": "1.170.8", + "@tanstack/router-core": "1.171.6", + "@tanstack/start-client-core": "1.170.4" }, "engines": { "node": ">=22.12.0" @@ -2149,16 +2159,16 @@ } }, "node_modules/@tanstack/react-start-server": { - "version": "1.166.22", - "resolved": "https://registry.npmjs.org/@tanstack/react-start-server/-/react-start-server-1.166.22.tgz", - "integrity": "sha512-Ce0WsGkxzZxhithG23EObJxJ3MPhiZpNbsqRSXyDJ/ccKLRNyjlO0frPxqyVM21/geugv7HVBbU8Cx4TB54dLQ==", + "version": "1.167.9", + "resolved": "https://registry.npmjs.org/@tanstack/react-start-server/-/react-start-server-1.167.9.tgz", + "integrity": "sha512-a1SGeeoIEg411vEN6DThB2Bm5tiYBb0tCC/RaG8BSjRVtsY6kxD9cP1+LOpZwjRSgfdyqtSbe1v78ZDB9z0/uw==", "license": "MIT", "dependencies": { - "@tanstack/history": "1.161.6", - "@tanstack/react-router": "1.168.7", - "@tanstack/router-core": "1.168.6", - "@tanstack/start-client-core": "1.167.6", - "@tanstack/start-server-core": "1.167.6" + "@tanstack/history": "1.162.0", + "@tanstack/react-router": "1.170.8", + "@tanstack/router-core": "1.171.6", + "@tanstack/start-client-core": "1.170.4", + "@tanstack/start-server-core": "1.169.4" }, "engines": { "node": ">=22.12.0" @@ -2172,6 +2182,49 @@ "react-dom": ">=18.0.0 || >=19.0.0" } }, + "node_modules/@tanstack/react-start/node_modules/@tanstack/react-start-rsc": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@tanstack/react-start-rsc/-/react-start-rsc-0.1.13.tgz", + "integrity": "sha512-nl5pKkxy1RnRxOLjy/c3g/RKdQSQYWzK5iuLlsRaO9TbLuMhQlNAn255xQgVXG56G9xCtDg8/nD0ZycxSlSkWA==", + "license": "MIT", + "dependencies": { + "@tanstack/react-router": "1.170.8", + "@tanstack/react-start-server": "1.167.9", + "@tanstack/router-core": "1.171.6", + "@tanstack/router-utils": "1.162.1", + "@tanstack/start-client-core": "1.170.4", + "@tanstack/start-fn-stubs": "1.162.0", + "@tanstack/start-plugin-core": "1.171.6", + "@tanstack/start-server-core": "1.169.4", + "@tanstack/start-storage-context": "1.167.8", + "pathe": "^2.0.3" + }, + "engines": { + "node": ">=22.12.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@rspack/core": ">=2.0.0-0", + "@vitejs/plugin-rsc": ">=0.5.20", + "react": ">=18.0.0 || >=19.0.0", + "react-dom": ">=18.0.0 || >=19.0.0", + "react-server-dom-rspack": ">=0.0.2" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "@vitejs/plugin-rsc": { + "optional": true + }, + "react-server-dom-rspack": { + "optional": true + } + } + }, "node_modules/@tanstack/react-store": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.9.3.tgz", @@ -2191,18 +2244,15 @@ } }, "node_modules/@tanstack/router-core": { - "version": "1.168.6", - "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.168.6.tgz", - "integrity": "sha512-okCno3pImpLFQMJ/4zqEIGjIV5yhxLGj0JByrzQDQehORN1y1q6lJUezT0KPK5qCQiKUApeWaboLPjgBVx1kaQ==", + "version": "1.171.6", + "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.171.6.tgz", + "integrity": "sha512-Ol6DQ+j6rf/rPVELIzo8LHwOQV2KL+zry3b+39kL/GKrt7YId52WJRAFMzuseY4XceSW+PU7sG/Cc1QkwJr0hg==", "license": "MIT", "dependencies": { - "@tanstack/history": "1.161.6", - "cookie-es": "^2.0.0", - "seroval": "^1.4.2", - "seroval-plugins": "^1.4.2" - }, - "bin": { - "intent": "bin/intent.js" + "@tanstack/history": "1.162.0", + "cookie-es": "^3.0.0", + "seroval": "^1.5.4", + "seroval-plugins": "^1.5.4" }, "engines": { "node": ">=20.19" @@ -2213,9 +2263,9 @@ } }, "node_modules/@tanstack/router-devtools-core": { - "version": "1.167.1", - "resolved": "https://registry.npmjs.org/@tanstack/router-devtools-core/-/router-devtools-core-1.167.1.tgz", - "integrity": "sha512-ECMM47J4KmifUvJguGituSiBpfN8SyCUEoxQks5RY09hpIBfR2eswCv2e6cJimjkKwBQXOVTPkTUk/yRvER+9w==", + "version": "1.168.0", + "resolved": "https://registry.npmjs.org/@tanstack/router-devtools-core/-/router-devtools-core-1.168.0.tgz", + "integrity": "sha512-wQoQhlBK7nlZgqzaqdYXKWNTpdHdsaREdaPhFZVH0/Ador+F+eM3/NF2i3f2LPeS0GgKraZUQXe1Q/1+KHyEYg==", "license": "MIT", "dependencies": { "clsx": "^2.1.1", @@ -2229,7 +2279,7 @@ "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "@tanstack/router-core": "^1.168.2", + "@tanstack/router-core": "^1.170.0", "csstype": "^3.0.10" }, "peerDependenciesMeta": { @@ -2239,19 +2289,19 @@ } }, "node_modules/@tanstack/router-generator": { - "version": "1.166.21", - "resolved": "https://registry.npmjs.org/@tanstack/router-generator/-/router-generator-1.166.21.tgz", - "integrity": "sha512-pJWsP6HaGrkIkfkcg6vzKyCBMbf1vV1BrQH+bFAVzXj3T/afmix3IPV2hiAj4zzjMxuddJD1on0Hn5+WDYA7zQ==", + "version": "1.167.10", + "resolved": "https://registry.npmjs.org/@tanstack/router-generator/-/router-generator-1.167.10.tgz", + "integrity": "sha512-CjbjWRSo6djLU/C7ncb9IbKUcf4IwpdqhLGngkwKkXaVFXGxEAafA/uhvOCv/UEUVR7NI3tJqqQmxYXGcJPbjw==", "license": "MIT", "dependencies": { - "@tanstack/router-core": "1.168.6", - "@tanstack/router-utils": "1.161.6", - "@tanstack/virtual-file-routes": "1.161.7", + "@babel/types": "^7.28.5", + "@tanstack/router-core": "1.171.6", + "@tanstack/router-utils": "1.162.1", + "@tanstack/virtual-file-routes": "1.162.0", + "jiti": "^2.7.0", + "magic-string": "^0.30.21", "prettier": "^3.5.0", - "recast": "^0.23.11", - "source-map": "^0.7.4", - "tsx": "^4.19.2", - "zod": "^3.24.2" + "zod": "^4.4.3" }, "engines": { "node": ">=20.19" @@ -2262,9 +2312,9 @@ } }, "node_modules/@tanstack/router-plugin": { - "version": "1.167.8", - "resolved": "https://registry.npmjs.org/@tanstack/router-plugin/-/router-plugin-1.167.8.tgz", - "integrity": "sha512-/X4ACYsSX4bRmomj5X2TBU75cHuIVI99Fsax6DWnP6hPb4PaSjPUHVBfHhk2NemJzEOZu1L31UQ9QDlbHU4ZTQ==", + "version": "1.168.11", + "resolved": "https://registry.npmjs.org/@tanstack/router-plugin/-/router-plugin-1.168.11.tgz", + "integrity": "sha512-b2eom/8xCWL/OiWxKub8kYsr8p+kvmB/eXwYGqCWG8vilcJo+eQCSyp54nKt0AZ5k/ET1+eINc+4mwL3bVeAgg==", "license": "MIT", "dependencies": { "@babel/core": "^7.28.5", @@ -2273,16 +2323,13 @@ "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", - "@tanstack/router-core": "1.168.6", - "@tanstack/router-generator": "1.166.21", - "@tanstack/router-utils": "1.161.6", - "@tanstack/virtual-file-routes": "1.161.7", - "chokidar": "^3.6.0", - "unplugin": "^2.1.2", - "zod": "^3.24.2" - }, - "bin": { - "intent": "bin/intent.js" + "@tanstack/router-core": "1.171.6", + "@tanstack/router-generator": "1.167.10", + "@tanstack/router-utils": "1.162.1", + "@tanstack/virtual-file-routes": "1.162.0", + "chokidar": "^5.0.0", + "unplugin": "^3.0.0", + "zod": "^4.4.3" }, "engines": { "node": ">=20.19" @@ -2292,10 +2339,10 @@ "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "@rsbuild/core": ">=1.0.2", - "@tanstack/react-router": "^1.168.7", - "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0", - "vite-plugin-solid": "^2.11.10", + "@rsbuild/core": ">=1.0.2 || ^2.0.0", + "@tanstack/react-router": "^1.170.8", + "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0", + "vite-plugin-solid": "^2.11.10 || ^3.0.0-0", "webpack": ">=5.92.0" }, "peerDependenciesMeta": { @@ -2317,9 +2364,9 @@ } }, "node_modules/@tanstack/router-ssr-query-core": { - "version": "1.167.0", - "resolved": "https://registry.npmjs.org/@tanstack/router-ssr-query-core/-/router-ssr-query-core-1.167.0.tgz", - "integrity": "sha512-+fpK1U+NR8YzcUmXhEy2tdPfT/XxIn1AMd/ODkYGMExAAUWnV8Zptptf41djK5eBj6718P6YTfxLRkxtfUdnVA==", + "version": "1.169.0", + "resolved": "https://registry.npmjs.org/@tanstack/router-ssr-query-core/-/router-ssr-query-core-1.169.0.tgz", + "integrity": "sha512-zueXiVsF1BbVc8iaalHILRGURDCVlTTOVdUy/36VVeKVKr778vqJzyus+erEoVu5x4vl4DBGGM8RHqCaus1TQQ==", "license": "MIT", "engines": { "node": ">=20.19" @@ -2334,9 +2381,9 @@ } }, "node_modules/@tanstack/router-utils": { - "version": "1.161.6", - "resolved": "https://registry.npmjs.org/@tanstack/router-utils/-/router-utils-1.161.6.tgz", - "integrity": "sha512-nRcYw+w2OEgK6VfjirYvGyPLOK+tZQz1jkYcmH5AjMamQ9PycnlxZF2aEZtPpNoUsaceX2bHptn6Ub5hGXqNvw==", + "version": "1.162.1", + "resolved": "https://registry.npmjs.org/@tanstack/router-utils/-/router-utils-1.162.1.tgz", + "integrity": "sha512-62layyTGmclHDQS/eidwKRfN1hhCKwViG7iEBcVmL0MXgcAB3OOucWCEcDDGd9Cu11H6b4QQ5oOo47MWIqwz0A==", "license": "MIT", "dependencies": { "@babel/core": "^7.28.5", @@ -2358,18 +2405,15 @@ } }, "node_modules/@tanstack/start-client-core": { - "version": "1.167.6", - "resolved": "https://registry.npmjs.org/@tanstack/start-client-core/-/start-client-core-1.167.6.tgz", - "integrity": "sha512-SmGQMSY5DEP8ZvfT1IGp0prUYdlEX0mbd1Dz+lAfma8+kA0u1Aa/IDALuQsahbga6VJ+um8KFyCNtvKHKEying==", + "version": "1.170.4", + "resolved": "https://registry.npmjs.org/@tanstack/start-client-core/-/start-client-core-1.170.4.tgz", + "integrity": "sha512-j/Deupf0zR7P5QObN38xTHufCRZkWTb6a/7aauu8eBmzOzDVggvuEdYHRZWiwJ9HRKbR2/SIJASVKeTtj1OcWw==", "license": "MIT", "dependencies": { - "@tanstack/router-core": "1.168.6", - "@tanstack/start-fn-stubs": "1.161.6", - "@tanstack/start-storage-context": "1.166.20", - "seroval": "^1.4.2" - }, - "bin": { - "intent": "bin/intent.js" + "@tanstack/router-core": "1.171.6", + "@tanstack/start-fn-stubs": "1.162.0", + "@tanstack/start-storage-context": "1.167.8", + "seroval": "^1.5.4" }, "engines": { "node": ">=22.12.0" @@ -2380,9 +2424,9 @@ } }, "node_modules/@tanstack/start-fn-stubs": { - "version": "1.161.6", - "resolved": "https://registry.npmjs.org/@tanstack/start-fn-stubs/-/start-fn-stubs-1.161.6.tgz", - "integrity": "sha512-Y6QSlGiLga8cHfvxGGaonXIlt2bIUTVdH6AMjmpMp7+ANNCp+N96GQbjjhLye3JkaxDfP68x5iZA8NK4imgRig==", + "version": "1.162.0", + "resolved": "https://registry.npmjs.org/@tanstack/start-fn-stubs/-/start-fn-stubs-1.162.0.tgz", + "integrity": "sha512-QWfUZ3Yo923tdQn38LyKMU8rcTw69zc+T4dAvgTWV4O56SqFRsGfS0lSWIMhJRwXIx/bvdi7nTUBDdZtTHtpTQ==", "license": "MIT", "engines": { "node": ">=22.12.0" @@ -2393,32 +2437,33 @@ } }, "node_modules/@tanstack/start-plugin-core": { - "version": "1.167.13", - "resolved": "https://registry.npmjs.org/@tanstack/start-plugin-core/-/start-plugin-core-1.167.13.tgz", - "integrity": "sha512-rKS02qj9/X3rRKAe/93INtR/1EGawN1vgy9fULtMKXvwWC8ZSU3VAs9zjPg8nHjy3fVE3SSL6NxFr4LJmdPYyg==", + "version": "1.171.6", + "resolved": "https://registry.npmjs.org/@tanstack/start-plugin-core/-/start-plugin-core-1.171.6.tgz", + "integrity": "sha512-e0AUN+omib0qLgs0r3zoKRSeHEkwL8qs8skvbl8zgDQXw9zF73K7ZXE7QarSzbqfLAiehVqlv0iPETp8ogUftQ==", "license": "MIT", "dependencies": { "@babel/code-frame": "7.27.1", "@babel/core": "^7.28.5", "@babel/types": "^7.28.5", - "@rolldown/pluginutils": "1.0.0-beta.40", - "@tanstack/router-core": "1.168.6", - "@tanstack/router-generator": "1.166.21", - "@tanstack/router-plugin": "1.167.8", - "@tanstack/router-utils": "1.161.6", - "@tanstack/start-client-core": "1.167.6", - "@tanstack/start-server-core": "1.167.6", - "cheerio": "^1.0.0", + "@rolldown/pluginutils": "1.0.1", + "@tanstack/router-core": "1.171.6", + "@tanstack/router-generator": "1.167.10", + "@tanstack/router-plugin": "1.168.11", + "@tanstack/router-utils": "1.162.1", + "@tanstack/start-client-core": "1.170.4", + "@tanstack/start-server-core": "1.169.4", "exsolve": "^1.0.7", + "lightningcss": "^1.32.0", "pathe": "^2.0.3", "picomatch": "^4.0.3", + "seroval": "^1.5.4", "source-map": "^0.7.6", "srvx": "^0.11.9", "tinyglobby": "^0.2.15", "ufo": "^1.5.4", "vitefu": "^1.1.1", "xmlbuilder2": "^4.0.3", - "zod": "^3.24.2" + "zod": "^4.4.3" }, "engines": { "node": ">=22.12.0" @@ -2428,7 +2473,16 @@ "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { + "@rsbuild/core": "^2.0.0", "vite": ">=7.0.0" + }, + "peerDependenciesMeta": { + "@rsbuild/core": { + "optional": true + }, + "vite": { + "optional": true + } } }, "node_modules/@tanstack/start-plugin-core/node_modules/@babel/code-frame": { @@ -2446,20 +2500,18 @@ } }, "node_modules/@tanstack/start-server-core": { - "version": "1.167.6", - "resolved": "https://registry.npmjs.org/@tanstack/start-server-core/-/start-server-core-1.167.6.tgz", - "integrity": "sha512-IC1U3SMM2SVZ3M9KMSHjV0AqAU3snGtGz6D3psrX8RZxTuMpmv/DaCs8jqGwfZbB2D2EQNUxDrBOmFYr7m7dQw==", + "version": "1.169.4", + "resolved": "https://registry.npmjs.org/@tanstack/start-server-core/-/start-server-core-1.169.4.tgz", + "integrity": "sha512-iM3HamWRQPROuAb+22frV/+GkqG2a3rL0X14N+Y0Dt5OajrIumPuprOn9ldUXsbdg89RTBf1KoJNDPeYGOqH4g==", "license": "MIT", "dependencies": { - "@tanstack/history": "1.161.6", - "@tanstack/router-core": "1.168.6", - "@tanstack/start-client-core": "1.167.6", - "@tanstack/start-storage-context": "1.166.20", - "h3-v2": "npm:h3@2.0.1-rc.16", - "seroval": "^1.4.2" - }, - "bin": { - "intent": "bin/intent.js" + "@tanstack/history": "1.162.0", + "@tanstack/router-core": "1.171.6", + "@tanstack/start-client-core": "1.170.4", + "@tanstack/start-storage-context": "1.167.8", + "fetchdts": "^0.1.6", + "h3-v2": "npm:h3@2.0.1-rc.20", + "seroval": "^1.5.4" }, "engines": { "node": ">=22.12.0" @@ -2470,12 +2522,12 @@ } }, "node_modules/@tanstack/start-storage-context": { - "version": "1.166.20", - "resolved": "https://registry.npmjs.org/@tanstack/start-storage-context/-/start-storage-context-1.166.20.tgz", - "integrity": "sha512-eQQG+0V3NMpPKd7K6mgqO4vwOdpj66m9F7UxVOiazcUrv5CgAM6H240tyHKKeHRMjykRhkF1ubarUF2Yd+GTOg==", + "version": "1.167.8", + "resolved": "https://registry.npmjs.org/@tanstack/start-storage-context/-/start-storage-context-1.167.8.tgz", + "integrity": "sha512-y9T+bIIp1ihLAXyS2+r+UovSupfu4KydSXpnoeRsw/14/E0huJsX7xB/n6XXOdmDYAaJ2WGOrG9wYjzeIDuBAw==", "license": "MIT", "dependencies": { - "@tanstack/router-core": "1.168.6" + "@tanstack/router-core": "1.171.6" }, "engines": { "node": ">=22.12.0" @@ -2496,13 +2548,10 @@ } }, "node_modules/@tanstack/virtual-file-routes": { - "version": "1.161.7", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-file-routes/-/virtual-file-routes-1.161.7.tgz", - "integrity": "sha512-olW33+Cn+bsCsZKPwEGhlkqS6w3M2slFv11JIobdnCFKMLG97oAI2kWKdx5/zsywTL8flpnoIgaZZPlQTFYhdQ==", + "version": "1.162.0", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-file-routes/-/virtual-file-routes-1.162.0.tgz", + "integrity": "sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA==", "license": "MIT", - "bin": { - "intent": "bin/intent.js" - }, "engines": { "node": ">=20.19" }, @@ -2707,39 +2756,40 @@ "license": "MIT" }, "node_modules/@vitest/expect": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", - "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz", + "integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==", "dev": true, "license": "MIT", "dependencies": { + "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "tinyrainbow": "^2.0.0" + "@vitest/spy": "4.1.8", + "@vitest/utils": "4.1.8", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/mocker": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", - "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz", + "integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.2.4", + "@vitest/spy": "4.1.8", "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" + "magic-string": "^0.30.21" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "msw": { @@ -2751,42 +2801,42 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", - "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz", + "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^2.0.0" + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", - "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz", + "integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.2.4", - "pathe": "^2.0.3", - "strip-literal": "^3.0.0" + "@vitest/utils": "4.1.8", + "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", - "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz", + "integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", - "magic-string": "^0.30.17", + "@vitest/pretty-format": "4.1.8", + "@vitest/utils": "4.1.8", + "magic-string": "^0.30.21", "pathe": "^2.0.3" }, "funding": { @@ -2794,45 +2844,30 @@ } }, "node_modules/@vitest/spy": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", - "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz", + "integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==", "dev": true, "license": "MIT", - "dependencies": { - "tinyspy": "^4.0.3" - }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", - "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz", + "integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", - "loupe": "^3.1.4", - "tinyrainbow": "^2.0.0" + "@vitest/pretty-format": "4.1.8", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -2867,39 +2902,14 @@ } }, "node_modules/ansis": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", - "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.3.1.tgz", + "integrity": "sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==", "license": "ISC", "engines": { "node": ">=14" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2926,18 +2936,6 @@ "node": ">=12" } }, - "node_modules/ast-types": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", - "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/babel-dead-code-elimination": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/babel-dead-code-elimination/-/babel-dead-code-elimination-1.0.12.tgz", @@ -2972,36 +2970,6 @@ "require-from-string": "^2.0.2" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "license": "ISC" - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/browserslist": { "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", @@ -3044,16 +3012,6 @@ "node": ">=20.19.0" } }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/caniuse-lite": { "version": "1.0.30001781", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz", @@ -3075,18 +3033,11 @@ "license": "CC-BY-4.0" }, "node_modules/chai": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, "engines": { "node": ">=18" } @@ -3104,80 +3055,19 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/check-error": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", - "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, - "node_modules/cheerio": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", - "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", - "license": "MIT", - "dependencies": { - "cheerio-select": "^2.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.2", - "encoding-sniffer": "^0.2.1", - "htmlparser2": "^10.1.0", - "parse5": "^7.3.0", - "parse5-htmlparser2-tree-adapter": "^7.1.0", - "parse5-parser-stream": "^7.1.2", - "undici": "^7.19.0", - "whatwg-mimetype": "^4.0.0" - }, - "engines": { - "node": ">=20.18.1" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" - } - }, - "node_modules/cheerio-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", - "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-select": "^5.1.0", - "css-what": "^6.1.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^5.0.0" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 20.19.0" }, "funding": { "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" } }, "node_modules/clsx": { @@ -3196,27 +3086,11 @@ "license": "MIT" }, "node_modules/cookie-es": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-2.0.0.tgz", - "integrity": "sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-3.1.1.tgz", + "integrity": "sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==", "license": "MIT" }, - "node_modules/css-select": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", - "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, "node_modules/css-tree": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", @@ -3231,18 +3105,6 @@ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, - "node_modules/css-what": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", - "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -3342,16 +3204,6 @@ "dev": true, "license": "MIT" }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -3387,72 +3239,17 @@ "dev": true, "license": "MIT" }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" }, "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dotenv": { - "version": "17.4.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", - "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" + "url": "https://dotenvx.com" } }, "node_modules/electron-to-chromium": { @@ -3461,19 +3258,6 @@ "integrity": "sha512-QNQ5l45DzYytThO21403XN3FvK0hOkWDG8viNf6jqS42msJ8I4tGDSpBCgvDRRPnkffafiwAym2X2eHeGD2V0w==", "license": "ISC" }, - "node_modules/encoding-sniffer": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", - "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", - "license": "MIT", - "dependencies": { - "iconv-lite": "^0.6.3", - "whatwg-encoding": "^3.1.1" - }, - "funding": { - "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" - } - }, "node_modules/enhanced-resolve": { "version": "5.20.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", @@ -3487,22 +3271,10 @@ "node": ">=10.13.0" } }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", "dev": true, "license": "MIT" }, @@ -3556,19 +3328,6 @@ "node": ">=6" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -3612,17 +3371,11 @@ } } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } + "node_modules/fetchdts": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/fetchdts/-/fetchdts-0.1.7.tgz", + "integrity": "sha512-YoZjBdafyLIop9lSxXVI33oLD5kN31q4Td+CasofLLYeLXRFeOsuOw0Uo+XNRi9PZlbfdlN2GmRtm4tCEQ9/KA==", + "license": "MIT" }, "node_modules/fsevents": { "version": "2.3.3", @@ -3647,30 +3400,6 @@ "node": ">=6.9.0" } }, - "node_modules/get-tsconfig": { - "version": "4.13.7", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", - "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/globrex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", @@ -3695,13 +3424,13 @@ }, "node_modules/h3-v2": { "name": "h3", - "version": "2.0.1-rc.16", - "resolved": "https://registry.npmjs.org/h3/-/h3-2.0.1-rc.16.tgz", - "integrity": "sha512-h+pjvyujdo9way8qj6FUbhaQcHlR8FEq65EhTX9ViT5pK8aLj68uFl4hBkF+hsTJAH+H1END2Yv6hTIsabGfag==", + "version": "2.0.1-rc.20", + "resolved": "https://registry.npmjs.org/h3/-/h3-2.0.1-rc.20.tgz", + "integrity": "sha512-28ljodXuUp0fZovdiSRq4G9OgrxCztrJe5VdYzXAB7ueRvI7pIUqLU14Xi3XqdYJ/khXjfpUOOD2EQa6CmBgsg==", "license": "MIT", "dependencies": { - "rou3": "^0.8.0", - "srvx": "^0.11.9" + "rou3": "^0.8.1", + "srvx": "^0.11.13" }, "bin": { "h3": "bin/h3.mjs" @@ -3731,37 +3460,6 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/htmlparser2": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", - "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.2", - "entities": "^7.0.1" - } - }, - "node_modules/htmlparser2/node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -3790,60 +3488,6 @@ "node": ">= 14" } }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -3861,9 +3505,9 @@ } }, "node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -3876,9 +3520,19 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -4135,6 +3789,9 @@ "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -4155,6 +3812,9 @@ "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -4175,6 +3835,9 @@ "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -4195,6 +3858,9 @@ "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -4248,13 +3914,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/loupe": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", - "dev": true, - "license": "MIT" - }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -4405,9 +4064,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "funding": [ { "type": "github", @@ -4428,74 +4087,18 @@ "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", "license": "MIT" }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "license": "MIT", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", - "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", - "license": "MIT", - "dependencies": { - "domhandler": "^5.0.3", - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-parser-stream": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", - "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "node_modules/obug": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.2.tgz", + "integrity": "sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], "license": "MIT", - "dependencies": { - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "license": "BSD-2-Clause", "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "node": ">=12.20.0" } }, "node_modules/pathe": { @@ -4504,16 +4107,6 @@ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "license": "MIT" }, - "node_modules/pathval": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -4533,9 +4126,9 @@ } }, "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "funding": [ { "type": "opencollective", @@ -4552,7 +4145,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -4575,9 +4168,9 @@ } }, "node_modules/prettier": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", - "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -4652,52 +4245,16 @@ } }, "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">= 20.19.0" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/recast": { - "version": "0.23.11", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", - "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", - "license": "MIT", - "dependencies": { - "ast-types": "^0.16.1", - "esprima": "~4.0.0", - "source-map": "~0.6.1", - "tiny-invariant": "^1.3.3", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/recast/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/require-from-string": { @@ -4710,15 +4267,6 @@ "node": ">=0.10.0" } }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, "node_modules/rollup": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz", @@ -4769,12 +4317,6 @@ "integrity": "sha512-ePa+XGk00/3HuCqrEnK3LxJW7I0SdNg6EFzKUJG73hMAdDcOUC/i/aSz7LSDwLrGr33kal/rqOGydzwl6U7zBA==", "license": "MIT" }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -4804,18 +4346,18 @@ } }, "node_modules/seroval": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.5.1.tgz", - "integrity": "sha512-OwrZRZAfhHww0WEnKHDY8OM0U/Qs8OTfIDWhUD4BLpNJUfXK4cGmjiagGze086m+mhI+V2nD0gfbHEnJjb9STA==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.5.4.tgz", + "integrity": "sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw==", "license": "MIT", "engines": { "node": ">=10" } }, "node_modules/seroval-plugins": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.5.1.tgz", - "integrity": "sha512-4FbuZ/TMl02sqv0RTFexu0SP6V+ywaIe5bAWCCEik0fk17BhALgwvUDVF7e3Uvf9pxmwCEJsRPmlkUE6HdzLAw==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.5.4.tgz", + "integrity": "sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw==", "license": "MIT", "engines": { "node": ">=10" @@ -4883,9 +4425,9 @@ } }, "node_modules/srvx": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/srvx/-/srvx-0.11.13.tgz", - "integrity": "sha512-oknN6qduuMPafxKtHucUeG32Q963pjriA5g3/Bl05cwEsUe5VVbIU4qR9LrALHbipSCyBe+VmfDGGydqazDRkw==", + "version": "0.11.16", + "resolved": "https://registry.npmjs.org/srvx/-/srvx-0.11.16.tgz", + "integrity": "sha512-bp07zRuycfTY43IjAvvTFnmnJi8ikW0VFiHwOhhYcVW/L4xQ1XY4PAd4Nuum1rsA17C39zL7x+CDhrn5AL32Rw==", "license": "MIT", "bin": { "srvx": "bin/srvx.mjs" @@ -4902,29 +4444,9 @@ "license": "MIT" }, "node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", - "dev": true, - "license": "MIT" - }, - "node_modules/strip-literal": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", - "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^9.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/strip-literal/node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", "dev": true, "license": "MIT" }, @@ -4954,12 +4476,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/tiny-invariant": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", - "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "license": "MIT" - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -4968,11 +4484,14 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/tinyglobby": { "version": "0.2.15", @@ -4990,30 +4509,10 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinypool": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, "node_modules/tinyrainbow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", - "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", - "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", "dev": true, "license": "MIT", "engines": { @@ -5040,18 +4539,6 @@ "dev": true, "license": "MIT" }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/tough-cookie": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", @@ -5099,31 +4586,6 @@ } } }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/tsx": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", - "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", - "license": "MIT", - "dependencies": { - "esbuild": "~0.27.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -5139,15 +4601,16 @@ } }, "node_modules/ufo": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", - "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", "license": "MIT" }, "node_modules/undici": { "version": "7.24.6", "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.6.tgz", "integrity": "sha512-Xi4agocCbRzt0yYMZGMA6ApD7gvtUFaxm4ZmeacWI4cZxaF6C+8I8QfofC20NAePiB/IcvZmzkJ7XPa471AEtA==", + "dev": true, "license": "MIT", "engines": { "node": ">=20.18.1" @@ -5161,18 +4624,17 @@ "license": "MIT" }, "node_modules/unplugin": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", - "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.0.0.tgz", + "integrity": "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==", "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.5", - "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" }, "engines": { - "node": ">=18.12.0" + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/update-browserslist-db": { @@ -5222,9 +4684,9 @@ "license": "MIT" }, "node_modules/vite": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", + "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "license": "MIT", "dependencies": { "esbuild": "^0.27.0", @@ -5295,29 +4757,6 @@ } } }, - "node_modules/vite-node": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", - "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.4.1", - "es-module-lexer": "^1.7.0", - "pathe": "^2.0.3", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, "node_modules/vite-tsconfig-paths": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz", @@ -5339,9 +4778,9 @@ } }, "node_modules/vitefu": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.2.tgz", - "integrity": "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", "license": "MIT", "workspaces": [ "tests/deps/*", @@ -5349,7 +4788,7 @@ "tests/projects/workspace/packages/*" ], "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0" + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "vite": { @@ -5358,65 +4797,79 @@ } }, "node_modules/vitest": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", - "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz", + "integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==", "dev": true, "license": "MIT", "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/expect": "3.2.4", - "@vitest/mocker": "3.2.4", - "@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.2.0", - "debug": "^4.4.1", - "expect-type": "^1.2.1", - "magic-string": "^0.30.17", + "@vitest/expect": "4.1.8", + "@vitest/mocker": "4.1.8", + "@vitest/pretty-format": "4.1.8", + "@vitest/runner": "4.1.8", + "@vitest/snapshot": "4.1.8", + "@vitest/spy": "4.1.8", + "@vitest/utils": "4.1.8", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", "pathe": "^2.0.3", - "picomatch": "^4.0.2", - "std-env": "^3.9.0", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", - "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.14", - "tinypool": "^1.1.1", - "tinyrainbow": "^2.0.0", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", - "vite-node": "3.2.4", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "@edge-runtime/vm": "*", - "@types/debug": "^4.1.12", - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.2.4", - "@vitest/ui": "3.2.4", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.8", + "@vitest/browser-preview": "4.1.8", + "@vitest/browser-webdriverio": "4.1.8", + "@vitest/coverage-istanbul": "4.1.8", + "@vitest/coverage-v8": "4.1.8", + "@vitest/ui": "4.1.8", "happy-dom": "*", - "jsdom": "*" + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "@edge-runtime/vm": { "optional": true }, - "@types/debug": { + "@opentelemetry/api": { "optional": true }, "@types/node": { "optional": true }, - "@vitest/browser": { + "@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": { @@ -5427,6 +4880,9 @@ }, "jsdom": { "optional": true + }, + "vite": { + "optional": false } } }, @@ -5459,28 +4915,6 @@ "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", "license": "MIT" }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", - "license": "MIT", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/whatwg-url": { "version": "16.0.1", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", @@ -5514,9 +4948,9 @@ } }, "node_modules/ws": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", - "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -5573,9 +5007,9 @@ "license": "ISC" }, "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/frameworks/javascript/tanstack/app/package.json b/frameworks/javascript/tanstack/app/package.json index 3361acc..1d00d45 100644 --- a/frameworks/javascript/tanstack/app/package.json +++ b/frameworks/javascript/tanstack/app/package.json @@ -20,11 +20,11 @@ "@tailwindcss/vite": "4.2.2", "@tanstack/react-devtools": "0.10.0", "@tanstack/react-query": "5.95.2", - "@tanstack/react-router": "1.168.7", - "@tanstack/react-router-devtools": "1.166.11", - "@tanstack/react-router-ssr-query": "1.166.10", - "@tanstack/react-start": "1.167.12", - "@tanstack/router-plugin": "1.167.8", + "@tanstack/react-router": "1.170.8", + "@tanstack/react-router-devtools": "1.167.0", + "@tanstack/react-router-ssr-query": "1.167.0", + "@tanstack/react-start": "1.168.13", + "@tanstack/router-plugin": "1.168.11", "lucide-react": "0.545.0", "mongodb": "7.1.1", "react": "19.2.4", @@ -43,14 +43,21 @@ "dotenv": "17.4.2", "jsdom": "28.1.0", "typescript": "5.9.3", - "vite": "7.3.1", + "vite": "7.3.2", "vite-tsconfig-paths": "5.1.4", - "vitest": "3.2.4" + "vitest": "4.1.8" }, "pnpm": { "onlyBuiltDependencies": [ "esbuild", "lightningcss" ] + }, + "overrides": { + "@tanstack/react-router": "1.170.8", + "@tanstack/router-core": "1.171.6", + "h3": ">=2.0.1-rc.18", + "postcss": ">=8.5.10", + "ws": ">=8.20.1" } } diff --git a/frameworks/javascript/tanstack/app/vitest.unit.config.ts b/frameworks/javascript/tanstack/app/vitest.unit.config.ts index 574607f..5713e15 100644 --- a/frameworks/javascript/tanstack/app/vitest.unit.config.ts +++ b/frameworks/javascript/tanstack/app/vitest.unit.config.ts @@ -52,6 +52,12 @@ export default defineConfig({ resolve: { alias: { '#': resolve(__dirname, './src'), + // Replace TanStack's createServerFn with a passthrough so server + // functions can be called directly in unit tests without Start context. + '@tanstack/react-start': resolve( + __dirname, + '../tests/integration/tanstack-react-start.mock.ts' + ), }, }, }); diff --git a/frameworks/javascript/tanstack/package-lock.json b/frameworks/javascript/tanstack/package-lock.json index d72a834..42d82ee 100644 --- a/frameworks/javascript/tanstack/package-lock.json +++ b/frameworks/javascript/tanstack/package-lock.json @@ -194,9 +194,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", "dev": true, "funding": [ { @@ -268,9 +268,9 @@ "license": "MIT" }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "dev": true, "license": "MIT" }, diff --git a/frameworks/javascript/tanstack/package.json b/frameworks/javascript/tanstack/package.json index ba168ef..49214b3 100644 --- a/frameworks/javascript/tanstack/package.json +++ b/frameworks/javascript/tanstack/package.json @@ -10,5 +10,9 @@ "devDependencies": { "bluehawk": "1.6.0", "prettier": "3.8.3" + }, + "overrides": { + "fast-uri": ">=3.1.2", + "lodash": ">=4.18.0" } } diff --git a/mflix/server/java-spring/pom.xml b/mflix/server/java-spring/pom.xml index a814870..938050b 100644 --- a/mflix/server/java-spring/pom.xml +++ b/mflix/server/java-spring/pom.xml @@ -8,7 +8,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.13 + 4.0.6 @@ -20,12 +20,11 @@ 21 - 2.8.13 + 3.0.3 5.1.0 - 3.20.0 1.13.0 1.17.8 - 1.11.0-beta19 + 1.15.0-beta25 @@ -50,7 +49,7 @@ me.paulschwarz - springboot3-dotenv + springboot4-dotenv ${dotenv.version} @@ -61,24 +60,11 @@ provided - - - org.apache.commons - commons-lang3 - ${commons.lang3.version} - - org.springdoc springdoc-openapi-starter-webmvc-ui ${springdoc.version} - - - org.apache.commons - commons-lang3 - - @@ -88,24 +74,31 @@ test + + + org.springframework.boot + spring-boot-starter-webmvc-test + test + + org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test org.testcontainers - mongodb + testcontainers-mongodb test - + - com.fasterxml.jackson.core - jackson-databind + org.springframework.boot + spring-boot-jackson2 diff --git a/mflix/server/java-spring/src/main/java/com/mongodb/samplemflix/config/WebMvcConfig.java b/mflix/server/java-spring/src/main/java/com/mongodb/samplemflix/config/WebMvcConfig.java index 44d4c5d..a0e5f83 100644 --- a/mflix/server/java-spring/src/main/java/com/mongodb/samplemflix/config/WebMvcConfig.java +++ b/mflix/server/java-spring/src/main/java/com/mongodb/samplemflix/config/WebMvcConfig.java @@ -47,21 +47,45 @@ protected void doFilterInternal( // If URL ends with trailing slash (but not root "/"), redirect to URL without it if (requestUri.length() > 1 && requestUri.endsWith("/")) { - String newUrl = requestUri.substring(0, requestUri.length() - 1); + String redirectPath = requestUri.substring(0, requestUri.length() - 1); + if (!isSafeRelativeRedirectPath(redirectPath)) { + filterChain.doFilter(request, response); + return; + } - // Preserve query string if present + String location = redirectPath; String queryString = request.getQueryString(); - if (queryString != null) { - newUrl += "?" + queryString; + if (queryString != null && !queryString.isBlank()) { + if (!isSafeQueryString(queryString)) { + filterChain.doFilter(request, response); + return; + } + location = redirectPath + "?" + queryString; } // Use 308 Permanent Redirect to preserve the HTTP method (POST, PATCH, DELETE, etc.) response.setStatus(HttpStatus.PERMANENT_REDIRECT.value()); - response.setHeader("Location", newUrl); + response.setHeader("Location", location); return; } filterChain.doFilter(request, response); } + + private static boolean isSafeRelativeRedirectPath(String path) { + return path.startsWith("/") + && !path.startsWith("//") + && !path.contains("://") + && !path.contains("\\") + && !path.contains("\0") + && !path.contains("\r") + && !path.contains("\n"); + } + + private static boolean isSafeQueryString(String queryString) { + return !queryString.contains("\r") + && !queryString.contains("\n") + && !queryString.contains("\0"); + } } } diff --git a/mflix/server/java-spring/src/main/java/com/mongodb/samplemflix/service/MovieServiceImpl.java b/mflix/server/java-spring/src/main/java/com/mongodb/samplemflix/service/MovieServiceImpl.java index 9aec741..b341289 100644 --- a/mflix/server/java-spring/src/main/java/com/mongodb/samplemflix/service/MovieServiceImpl.java +++ b/mflix/server/java-spring/src/main/java/com/mongodb/samplemflix/service/MovieServiceImpl.java @@ -307,10 +307,11 @@ private Query buildQuery(MovieSearchQuery query) { mongoQuery.addCriteria(textCriteria); } - // Genre filter (case-insensitive regex) + // Genre filter (case-insensitive literal match; escape user input for regex safety) if (query.genre() != null && !query.genre().trim().isEmpty()) { + String escapedGenre = Pattern.quote(query.genre().trim()); mongoQuery.addCriteria(Criteria.where(Movie.Fields.GENRES) - .regex(Pattern.compile(query.genre(), Pattern.CASE_INSENSITIVE))); + .regex(Pattern.compile(escapedGenre, Pattern.CASE_INSENSITIVE))); } // Year filter diff --git a/mflix/server/java-spring/src/main/resources/application.properties b/mflix/server/java-spring/src/main/resources/application.properties index 47bd6d0..1e89e1b 100644 --- a/mflix/server/java-spring/src/main/resources/application.properties +++ b/mflix/server/java-spring/src/main/resources/application.properties @@ -36,8 +36,7 @@ logging.logback.rollingpolicy.max-file-size=5MB logging.logback.rollingpolicy.max-history=5 # Jackson Configuration (JSON serialization) -spring.jackson.default-property-inclusion=non_null -spring.jackson.serialization.write-dates-as-timestamps=false +spring.jackson2.default-property-inclusion=non_null # API Documentation (Swagger/OpenAPI) springdoc.api-docs.path=/api-docs diff --git a/mflix/server/java-spring/src/test/java/com/mongodb/samplemflix/controller/MovieControllerTest.java b/mflix/server/java-spring/src/test/java/com/mongodb/samplemflix/controller/MovieControllerTest.java index 4eacf86..1b02f8f 100644 --- a/mflix/server/java-spring/src/test/java/com/mongodb/samplemflix/controller/MovieControllerTest.java +++ b/mflix/server/java-spring/src/test/java/com/mongodb/samplemflix/controller/MovieControllerTest.java @@ -34,7 +34,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest; import org.springframework.http.MediaType; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; diff --git a/mflix/server/js-express/package.json b/mflix/server/js-express/package.json index 4d79a40..1ffcdf4 100644 --- a/mflix/server/js-express/package.json +++ b/mflix/server/js-express/package.json @@ -22,6 +22,7 @@ "cors": "^2.8.6", "dotenv": "^17.2.4", "express": "^5.2.1", + "express-rate-limit": "^8.5.2", "mongodb": "^7.2.0", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", diff --git a/mflix/server/js-express/src/controllers/movieController.ts b/mflix/server/js-express/src/controllers/movieController.ts index a4e516b..0780510 100644 --- a/mflix/server/js-express/src/controllers/movieController.ts +++ b/mflix/server/js-express/src/controllers/movieController.ts @@ -25,6 +25,13 @@ import { validateRequiredFields, } from "../utils/errorHandler"; import logger from "../utils/logger"; +import { + convertFilterObjectIds, + escapeRegexLiteral, + InvalidMongoQueryError, + sanitizeBatchFilter, + sanitizeUpdateFields, +} from "../utils/mongoQuery"; import { CreateMovieRequest, UpdateMovieRequest, @@ -82,9 +89,12 @@ export async function getAllMovies(req: Request, res: Response): Promise { filter.$text = { $search: q }; } - // Genre filtering - if (genre) { - filter.genres = { $regex: new RegExp(genre, "i") }; + // Genre filtering (case-insensitive literal match; escape user input for regex safety) + const trimmedGenre = typeof genre === "string" ? genre.trim() : ""; + if (trimmedGenre) { + filter.genres = { + $regex: new RegExp(escapeRegexLiteral(trimmedGenre), "i"), + }; } // Year filtering @@ -325,11 +335,26 @@ export async function updateMovie(req: Request, res: Response): Promise { const moviesCollection = getCollection("movies"); + let sanitizedUpdate: UpdateMovieRequest; + try { + sanitizedUpdate = sanitizeUpdateFields( + updateData as Record + ); + } catch (error) { + if (error instanceof InvalidMongoQueryError) { + res + .status(400) + .json(createErrorResponse(error.message, "INVALID_UPDATE")); + return; + } + throw error; + } + // Use updateOne() to update a single document // $set operator replaces the value of fields with specified values const result = await moviesCollection.updateOne( { _id: new ObjectId(id) }, - { $set: updateData } + { $set: sanitizedUpdate } ); if (result.matchedCount === 0) { @@ -388,23 +413,51 @@ export async function updateMoviesBatch( const moviesCollection = getCollection("movies"); - // Handle ObjectId conversion for _id fields in $in queries - let processedFilter = { ...filter }; - if (filter._id && filter._id.$in && Array.isArray(filter._id.$in)) { - // Convert string IDs to ObjectId instances - processedFilter._id = { - $in: filter._id.$in.map((id: string) => { - if (ObjectId.isValid(id)) { - return new ObjectId(id); - } - throw new Error(`Invalid ObjectId: ${id}`); - }) - }; + let sanitizedFilter: Document; + let sanitizedUpdate: UpdateMovieRequest; + let processedFilter: Document; + + try { + sanitizedFilter = sanitizeBatchFilter(filter); + } catch (error) { + if (error instanceof InvalidMongoQueryError) { + res + .status(400) + .json(createErrorResponse(error.message, "INVALID_FILTER")); + return; + } + throw error; + } + + try { + sanitizedUpdate = sanitizeUpdateFields(update); + } catch (error) { + if (error instanceof InvalidMongoQueryError) { + res + .status(400) + .json(createErrorResponse(error.message, "INVALID_UPDATE")); + return; + } + throw error; + } + + try { + processedFilter = convertFilterObjectIds(sanitizedFilter); + } catch (error) { + if (error instanceof InvalidMongoQueryError) { + res + .status(400) + .json(createErrorResponse(error.message, "INVALID_OBJECT_ID")); + return; + } + throw error; } // Use updateMany() to update multiple documents // This is useful for bulk operations like updating all movies from a certain year - const result = await moviesCollection.updateMany(processedFilter, { $set: update }); + const result = await moviesCollection.updateMany(processedFilter, { + $set: sanitizedUpdate, + }); res.json( createSuccessResponse( @@ -483,18 +536,31 @@ export async function deleteMoviesBatch( const moviesCollection = getCollection("movies"); - // Handle ObjectId conversion for _id fields in $in queries - let processedFilter = { ...filter }; - if (filter._id && filter._id.$in && Array.isArray(filter._id.$in)) { - // Convert string IDs to ObjectId instances - processedFilter._id = { - $in: filter._id.$in.map((id: string) => { - if (ObjectId.isValid(id)) { - return new ObjectId(id); - } - throw new Error(`Invalid ObjectId: ${id}`); - }) - }; + let sanitizedFilter: Document; + let processedFilter: Document; + + try { + sanitizedFilter = sanitizeBatchFilter(filter); + } catch (error) { + if (error instanceof InvalidMongoQueryError) { + res + .status(400) + .json(createErrorResponse(error.message, "INVALID_FILTER")); + return; + } + throw error; + } + + try { + processedFilter = convertFilterObjectIds(sanitizedFilter); + } catch (error) { + if (error instanceof InvalidMongoQueryError) { + res + .status(400) + .json(createErrorResponse(error.message, "INVALID_OBJECT_ID")); + return; + } + throw error; } // Use deleteMany() to remove multiple documents diff --git a/mflix/server/js-express/src/middleware/rateLimiter.ts b/mflix/server/js-express/src/middleware/rateLimiter.ts new file mode 100644 index 0000000..e0e521a --- /dev/null +++ b/mflix/server/js-express/src/middleware/rateLimiter.ts @@ -0,0 +1,23 @@ +import rateLimit from "express-rate-limit"; +import { createErrorResponse } from "../utils/errorHandler"; + +/** + * Rate limiter for movie API routes that access the database. + * Override via RATE_LIMIT_MAX env var (e.g. in test setup). + */ +export const moviesRateLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, + max: process.env.RATE_LIMIT_MAX + ? parseInt(process.env.RATE_LIMIT_MAX, 10) + : 300, + standardHeaders: true, + legacyHeaders: false, + handler: (_req, res) => { + res.status(429).json( + createErrorResponse( + "Too many requests. Please try again later.", + "RATE_LIMIT_EXCEEDED" + ) + ); + }, +}); diff --git a/mflix/server/js-express/src/routes/movies.ts b/mflix/server/js-express/src/routes/movies.ts index 4be4df2..ea81091 100644 --- a/mflix/server/js-express/src/routes/movies.ts +++ b/mflix/server/js-express/src/routes/movies.ts @@ -18,9 +18,12 @@ import express from "express"; import { asyncHandler } from "../utils/errorHandler"; import * as movieController from "../controllers/movieController"; +import { moviesRateLimiter } from "../middleware/rateLimiter"; const router = express.Router(); +router.use(moviesRateLimiter); + /** * @swagger * /api/movies: diff --git a/mflix/server/js-express/src/utils/mongoQuery.ts b/mflix/server/js-express/src/utils/mongoQuery.ts new file mode 100644 index 0000000..3aad7f0 --- /dev/null +++ b/mflix/server/js-express/src/utils/mongoQuery.ts @@ -0,0 +1,135 @@ +import { Document, ObjectId } from "mongodb"; +import { UpdateMovieRequest } from "../types"; + +const MOVIE_FIELDS = [ + "title", + "year", + "plot", + "fullplot", + "genres", + "directors", + "writers", + "cast", + "countries", + "languages", + "rated", + "runtime", + "poster", +] as const; + +const ALLOWED_FILTER_FIELDS = new Set([...MOVIE_FIELDS, "_id"]); + +const ALLOWED_OPERATORS = new Set([ + "$in", + "$nin", + "$gt", + "$gte", + "$lt", + "$lte", + "$ne", + "$exists", +]); + +const UPDATE_FIELDS = [...MOVIE_FIELDS] as (keyof UpdateMovieRequest)[]; + +const UNSUPPORTED_FILTER_MESSAGE = + "Filter contains an unsupported field or operator"; +const UNSUPPORTED_UPDATE_MESSAGE = + "Update contains an unsupported field or operator"; + +export function escapeRegexLiteral(value: string): string { + return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +export class InvalidMongoQueryError extends Error { + constructor(message: string) { + super(message); + this.name = "InvalidMongoQueryError"; + } +} + +function sanitizeOperatorValue(value: unknown): unknown { + if (value === null || typeof value !== "object" || Array.isArray(value)) { + return value; + } + + const operatorMap = value as Record; + const sanitized: Record = {}; + + for (const [operator, operatorValue] of Object.entries(operatorMap)) { + if (!operator.startsWith("$") || !ALLOWED_OPERATORS.has(operator)) { + throw new InvalidMongoQueryError(UNSUPPORTED_FILTER_MESSAGE); + } + sanitized[operator] = operatorValue; + } + + return sanitized; +} + +export function sanitizeBatchFilter(filter: Record): Document { + if (!filter || typeof filter !== "object" || Array.isArray(filter)) { + throw new InvalidMongoQueryError("Filter must be a non-array object"); + } + + const sanitized: Document = {}; + + for (const [key, value] of Object.entries(filter)) { + if (key.startsWith("$") || !ALLOWED_FILTER_FIELDS.has(key)) { + throw new InvalidMongoQueryError(UNSUPPORTED_FILTER_MESSAGE); + } + + if (value !== null && typeof value === "object" && !Array.isArray(value)) { + sanitized[key] = sanitizeOperatorValue(value); + } else { + sanitized[key] = value; + } + } + + return sanitized; +} + +export function sanitizeUpdateFields( + update: Record +): UpdateMovieRequest { + if (!update || typeof update !== "object" || Array.isArray(update)) { + throw new InvalidMongoQueryError("Update must be a non-array object"); + } + + const sanitized: UpdateMovieRequest = {}; + + for (const key of Object.keys(update)) { + if ( + key.startsWith("$") || + !UPDATE_FIELDS.includes(key as keyof UpdateMovieRequest) + ) { + throw new InvalidMongoQueryError(UNSUPPORTED_UPDATE_MESSAGE); + } + (sanitized as Record)[key] = update[key]; + } + + return sanitized; +} + +export function convertFilterObjectIds(filter: Document): Document { + const processedFilter: Document = { ...filter }; + + if ( + processedFilter._id && + typeof processedFilter._id === "object" && + processedFilter._id !== null && + "$in" in processedFilter._id && + Array.isArray(processedFilter._id.$in) + ) { + processedFilter._id = { + $in: processedFilter._id.$in.map((id: unknown) => { + const idStr = String(id); + if (ObjectId.isValid(idStr)) { + return new ObjectId(idStr); + } + throw new InvalidMongoQueryError(UNSUPPORTED_FILTER_MESSAGE); + }), + }; + } + + return processedFilter; +} diff --git a/mflix/server/js-express/tests/controllers/movieController.test.ts b/mflix/server/js-express/tests/controllers/movieController.test.ts index 433994b..722cea9 100644 --- a/mflix/server/js-express/tests/controllers/movieController.test.ts +++ b/mflix/server/js-express/tests/controllers/movieController.test.ts @@ -480,7 +480,7 @@ describe("Movie Controller Tests", () => { describe("updateMoviesBatch", () => { it("should successfully update multiple movies", async () => { const filter = { year: 2023 }; - const update = { genre: "Updated Genre" }; + const update = { rated: "PG-13" }; const updateResult = { matchedCount: 5, modifiedCount: 3 }; mockRequest.body = { filter, update }; @@ -525,6 +525,40 @@ describe("Movie Controller Tests", () => { "EMPTY_UPDATE" ); }); + + it("should return 400 when update contains MongoDB operators", async () => { + mockRequest.body = { + filter: { year: 2023 }, + update: { $set: { title: "Injected" } }, + }; + + await updateMoviesBatch(mockRequest as Request, mockResponse as Response); + + expectErrorResponse( + mockStatus, + mockJson, + 400, + "Update contains an unsupported field or operator", + "INVALID_UPDATE" + ); + }); + + it("should return 400 when update contains disallowed fields", async () => { + mockRequest.body = { + filter: { year: 2023 }, + update: { genre: "Updated Genre" }, + }; + + await updateMoviesBatch(mockRequest as Request, mockResponse as Response); + + expectErrorResponse( + mockStatus, + mockJson, + 400, + "Update contains an unsupported field or operator", + "INVALID_UPDATE" + ); + }); }); describe("deleteMoviesBatch", () => { diff --git a/mflix/server/js-express/tests/integration/setup.ts b/mflix/server/js-express/tests/integration/setup.ts index bd628ea..9250132 100644 --- a/mflix/server/js-express/tests/integration/setup.ts +++ b/mflix/server/js-express/tests/integration/setup.ts @@ -11,6 +11,7 @@ import dotenv from "dotenv"; // Set test environment variables dotenv.config(); process.env.NODE_ENV = "test"; +process.env.RATE_LIMIT_MAX = "10000"; // Increase timeout for database operations in integration tests // Integration tests may take longer due to network calls and index creation diff --git a/mflix/server/js-express/tests/setup.ts b/mflix/server/js-express/tests/setup.ts index df081e6..a0f7aff 100644 --- a/mflix/server/js-express/tests/setup.ts +++ b/mflix/server/js-express/tests/setup.ts @@ -7,6 +7,7 @@ // Set test environment variables process.env.NODE_ENV = "test"; +process.env.RATE_LIMIT_MAX = "10000"; process.env.MONGODB_URI = "mongodb://localhost:27017/test_sample_mflix"; process.env.PORT = "3002"; diff --git a/mflix/server/python-fastapi/main.py b/mflix/server/python-fastapi/main.py index 81048f9..096f8b1 100644 --- a/mflix/server/python-fastapi/main.py +++ b/mflix/server/python-fastapi/main.py @@ -156,13 +156,17 @@ async def voyage_auth_error_handler(request: Request, exc: VoyageAuthError): @app.exception_handler(VoyageAPIError) async def voyage_api_error_handler(request: Request, exc: VoyageAPIError): - """Handle Voyage AI API errors with 503 status.""" + """Handle Voyage AI API errors using the exception's HTTP status code.""" + client_messages = { + 400: "Invalid vector search request.", + 429: "Vector search rate limit exceeded. Please try again later.", + 503: "Vector search service unavailable.", + } return JSONResponse( - status_code=503, + status_code=exc.status_code, content=create_error_response( - message="Vector search service unavailable", + message=client_messages.get(exc.status_code, "Vector search failed."), code="VOYAGE_API_ERROR", - details=exc.message ) ) diff --git a/mflix/server/python-fastapi/requirements.in b/mflix/server/python-fastapi/requirements.in index f798bfe..16306e0 100644 --- a/mflix/server/python-fastapi/requirements.in +++ b/mflix/server/python-fastapi/requirements.in @@ -2,8 +2,8 @@ # 1. CORE WEB FRAMEWORK & ASGI SERVER # FastAPI and its main components. # ------------------------------------------------------------------------------ -fastapi~=0.120.4 # The main web framework -starlette~=0.49.3 # FastAPI's underlying ASGI toolkit +fastapi~=0.136.3 # The main web framework (CVE-2026-48710 / PYSEC-2026-161) +starlette>=1.0.1 # Host header validation fix (PYSEC-2026-161) uvicorn~=0.38.0 # Production-ready ASGI server uvloop~=0.22.1 # Optional: High-performance event loop for uvicorn websockets~=15.0.1 # For WebSocket support @@ -15,7 +15,7 @@ watchfiles~=1.1.1 # For hot-reloading in development # ------------------------------------------------------------------------------ pydantic~=2.12.5 # Data validation and settings management python-dotenv>=1.2.2 # For loading configuration from .env files (CVE-2026-28684) -python-multipart>=0.0.22 # For parsing form data and file uploads +python-multipart>=0.0.29 # For parsing form data and file uploads (Dependabot #62) PyYAML~=6.0.3 # For handling YAML configuration or data # ============================================================================== @@ -32,7 +32,7 @@ dnspython~=2.8.0 # Required for SRV record lookups by pymongo (e.g., Mong httpx~=0.28.1 # Asynchronous HTTP client for requests to external APIs email-validator~=2.3.0 # Utility for validating email addresses voyageai~=0.3.7 # Vector embeddings API client -urllib3>=2.6.3 # HTTP library +urllib3>=2.7.0 # HTTP library (Dependabot #66, #67) # ============================================================================== # 5. CLI & DEVELOPMENT TOOLS @@ -46,8 +46,8 @@ fastapi-cloud-cli~=0.3.1 # Tools for cloud deployment (specific to your pipeline # 6. TESTING & MONITORING # Frameworks for ensuring code quality and production health. # ------------------------------------------------------------------------------ -pytest~=8.4.2 # Primary testing framework -pytest-asyncio~=1.2.0 # Plugin to make asynchronous tests easy with pytest +pytest~=9.0.3 # Primary testing framework (CVE-2025-71176 fix) +pytest-asyncio~=1.3.0 # Plugin to make asynchronous tests easy with pytest (pytest 9 compat) sentry-sdk~=2.42.1 # For error tracking and performance monitoring # ============================================================================== @@ -62,8 +62,12 @@ rich-toolkit~=0.15.1 # Extensions for the 'rich' library # Minimum versions for indirect dependencies. # ------------------------------------------------------------------------------ filelock>=3.20.3 # Transitive dep via huggingface-hub -aiohttp>=3.13.4 # Transitive dep via voyageai (CVE-2026-34525) +aiohttp>=3.14.0 # Transitive dep via voyageai (Dependabot #74, #75) orjson>=3.11.7 # Transitive dep via langsmith (CVE fix) -langchain-core>=1.2.11 # Transitive dep via langchain-text-splitters (CVE-2026-26013 fix) +langchain-core>=1.4.0 # Transitive dep via langchain-text-splitters (Dependabot #63) +langsmith>=0.8.5 # Transitive dep via langchain-core (Dependabot #68) +langchain-text-splitters>=1.1.2 # Transitive dep via langchain (CVE-2026-41481) +pygments>=2.20.0 # Transitive dep via rich/pytest (CVE-2026-4539) pillow>=12.2.0 # Transitive dep via voyageai (Pillow 12.2.0 security fixes) requests>=2.33.0 # Transitive dep via langsmith/voyageai (CVE-2026-25645 fix) +idna>=3.15 # Transitive dep via httpx/requests (Dependabot #70) diff --git a/mflix/server/python-fastapi/requirements.txt b/mflix/server/python-fastapi/requirements.txt index 4a51bab..dc88e6a 100644 --- a/mflix/server/python-fastapi/requirements.txt +++ b/mflix/server/python-fastapi/requirements.txt @@ -2,266 +2,82 @@ # This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --output-file=requirements.txt requirements.in +# pip-compile --no-annotate --output-file=requirements.txt --strip-extras requirements.in # -aiohappyeyeballs==2.6.1 - # via aiohttp -aiohttp==3.13.5 - # via - # -r requirements.in - # voyageai +aiohappyeyeballs==2.6.2 +aiohttp==3.14.0 aiolimiter==1.2.1 - # via voyageai aiosignal==1.4.0 - # via aiohttp annotated-doc==0.0.4 - # via fastapi annotated-types==0.7.0 - # via pydantic -anyio==4.12.1 - # via - # httpx - # starlette - # watchfiles -attrs==25.4.0 - # via aiohttp -certifi==2026.1.4 - # via - # httpcore - # httpx - # requests - # sentry-sdk -charset-normalizer==3.4.4 - # via requests -click==8.3.1 - # via - # rich-toolkit - # typer - # typer-slim - # uvicorn +anyio==4.13.0 +attrs==26.1.0 +certifi==2026.5.20 +charset-normalizer==3.4.7 +click==8.4.1 dnspython==2.8.0 - # via - # -r requirements.in - # email-validator - # pymongo email-validator==2.3.0 - # via - # -r requirements.in - # pydantic -fastapi==0.120.4 - # via -r requirements.in -fastapi-cli==0.0.20 - # via -r requirements.in +fastapi==0.136.3 +fastapi-cli==0.0.24 fastapi-cloud-cli==0.3.1 - # via -r requirements.in ffmpeg-python==0.2.0 - # via voyageai -filelock==3.20.3 - # via - # -r requirements.in - # huggingface-hub +filelock==3.29.1 frozenlist==1.8.0 - # via - # aiohttp - # aiosignal -fsspec==2026.2.0 - # via huggingface-hub +fsspec==2026.4.0 future==1.0.0 - # via ffmpeg-python h11==0.16.0 - # via - # httpcore - # uvicorn -hf-xet==1.2.0 - # via huggingface-hub +hf-xet==1.5.0 httpcore==1.0.9 - # via httpx -httptools==0.7.1 - # via uvicorn +httptools==0.8.0 httpx==0.28.1 - # via - # -r requirements.in - # fastapi-cloud-cli - # huggingface-hub - # langsmith -huggingface-hub==1.4.1 - # via tokenizers -idna==3.11 - # via - # anyio - # email-validator - # httpx - # requests - # yarl +huggingface-hub==1.17.0 +idna==3.18 iniconfig==2.3.0 - # via pytest jsonpatch==1.33 - # via langchain-core -jsonpointer==3.0.0 - # via jsonpatch -langchain-core==1.2.11 - # via - # -r requirements.in - # langchain-text-splitters -langchain-text-splitters==1.1.0 - # via voyageai -langsmith==0.6.9 - # via langchain-core -markdown-it-py==4.0.0 - # via rich +jsonpointer==3.1.1 +langchain-core==1.4.0 +langchain-protocol==0.0.16 +langchain-text-splitters==1.1.2 +langsmith==0.8.9 +markdown-it-py==4.2.0 mdurl==0.1.2 - # via markdown-it-py multidict==6.7.1 - # via - # aiohttp - # yarl -numpy==2.4.2 - # via voyageai -orjson==3.11.7 - # via - # -r requirements.in - # langsmith -packaging==26.0 - # via - # huggingface-hub - # langchain-core - # langsmith - # pytest +numpy==2.4.6 +orjson==3.11.9 +packaging==26.2 pillow==12.2.0 - # via - # -r requirements.in - # voyageai pluggy==1.6.0 - # via pytest -propcache==0.4.1 - # via - # aiohttp - # yarl -pydantic[email]==2.12.5 - # via - # -r requirements.in - # fastapi - # fastapi-cloud-cli - # langchain-core - # langsmith - # voyageai +propcache==0.5.2 +pydantic==2.12.5 pydantic-core==2.41.5 - # via pydantic -pygments==2.19.2 - # via - # pytest - # rich +pygments==2.20.0 pymongo==4.17.0 - # via -r requirements.in -pytest==8.4.2 - # via - # -r requirements.in - # pytest-asyncio -pytest-asyncio==1.2.0 - # via -r requirements.in +pytest==9.0.3 +pytest-asyncio==1.3.0 python-dotenv==1.2.2 - # via - # -r requirements.in - # uvicorn -python-multipart==0.0.22 - # via -r requirements.in +python-multipart==0.0.31 pyyaml==6.0.3 - # via - # -r requirements.in - # huggingface-hub - # langchain-core - # uvicorn -requests==2.33.0 - # via - # -r requirements.in - # langsmith - # requests-toolbelt - # voyageai +requests==2.34.2 requests-toolbelt==1.0.0 - # via langsmith rich==14.2.0 - # via - # -r requirements.in - # rich-toolkit - # typer rich-toolkit==0.15.1 - # via - # -r requirements.in - # fastapi-cli - # fastapi-cloud-cli rignore==0.7.6 - # via fastapi-cloud-cli sentry-sdk==2.42.1 - # via - # -r requirements.in - # fastapi-cloud-cli shellingham==1.5.4 - # via - # huggingface-hub - # typer -starlette==0.49.3 - # via - # -r requirements.in - # fastapi -tenacity==9.1.3 - # via - # langchain-core - # voyageai -tokenizers==0.22.2 - # via voyageai +starlette==1.2.1 +tenacity==9.1.4 +tokenizers==0.23.1 tqdm==4.67.3 - # via huggingface-hub typer==0.20.1 - # via - # -r requirements.in - # fastapi-cli - # fastapi-cloud-cli -typer-slim==0.21.1 - # via huggingface-hub typing-extensions==4.15.0 - # via - # fastapi - # huggingface-hub - # langchain-core - # pydantic - # pydantic-core - # rich-toolkit - # typer - # typer-slim - # typing-inspection typing-inspection==0.4.2 - # via pydantic -urllib3==2.6.3 - # via - # -r requirements.in - # requests - # sentry-sdk -uuid-utils==0.14.0 - # via - # langchain-core - # langsmith -uvicorn[standard]==0.38.0 - # via - # -r requirements.in - # fastapi-cli - # fastapi-cloud-cli +urllib3==2.7.0 +uuid-utils==0.16.0 +uvicorn==0.38.0 uvloop==0.22.1 - # via - # -r requirements.in - # uvicorn voyageai==0.3.7 - # via -r requirements.in watchfiles==1.1.1 - # via - # -r requirements.in - # uvicorn websockets==15.0.1 - # via - # -r requirements.in - # uvicorn -xxhash==3.6.0 - # via langsmith -yarl==1.22.0 - # via aiohttp +xxhash==3.7.0 +yarl==1.24.2 zstandard==0.25.0 - # via langsmith diff --git a/mflix/server/python-fastapi/src/routers/movies.py b/mflix/server/python-fastapi/src/routers/movies.py index 50170eb..f745437 100644 --- a/mflix/server/python-fastapi/src/routers/movies.py +++ b/mflix/server/python-fastapi/src/routers/movies.py @@ -4,7 +4,7 @@ from src.models.models import VectorSearchResult, CreateMovieRequest, Movie, SuccessResponse, UpdateMovieRequest, SearchMoviesResponse from typing import Any, List, Optional from src.utils.successResponse import create_success_response -from src.utils.errorResponse import create_error_response +from src.utils.errorResponse import create_error_response, server_error_response from src.utils.response_docs import ( VECTOR_SEARCH_RESPONSES, OBJECTID_VALIDATION_RESPONSES, @@ -14,6 +14,7 @@ CRUD_WITH_OBJECTID_RESPONSES ) from src.utils.exceptions import VoyageAuthError, VoyageAPIError +from src.utils.logger import logger from bson import ObjectId, errors import re from bson.errors import InvalidId @@ -276,13 +277,11 @@ async def search_movies( # Execute the aggregation pipeline using the helper function try: results = await execute_aggregation(aggregation_pipeline) - except Exception as e: - return JSONResponse( - status_code=500, - content=create_error_response( - message=f"An error occurred while performing the search: {str(e)}", - code="SEARCH_ERROR" - ) + except Exception: + return server_error_response( + "An error occurred while performing the search.", + "SEARCH_ERROR", + log_context="search_movies", ) @@ -443,17 +442,11 @@ async def vector_search_movies( except VoyageAPIError: # Re-raise custom exceptions to be handled by the exception handlers raise - except Exception as e: - # Log the error for debugging - print(f"Vector search error: {str(e)}") - - # Handle generic errors - return JSONResponse( - status_code=500, - content=create_error_response( - message=f"Error performing vector search: {str(e)}", - code="VECTOR_SEARCH_ERROR" - ) + except Exception: + return server_error_response( + "Error performing vector search.", + "VECTOR_SEARCH_ERROR", + log_context="vector_search_movies", ) """ @@ -480,13 +473,11 @@ async def get_distinct_genres(): # Use distinct() to get all unique values from the genres array field # MongoDB automatically flattens array fields when using distinct() genres = await movies_collection.distinct("genres") - except Exception as e: - return JSONResponse( - status_code=500, - content=create_error_response( - message=f"Database error occurred: {str(e)}", - code="DATABASE_ERROR" - ) + except Exception: + return server_error_response( + "Database error occurred.", + "DATABASE_ERROR", + log_context="get_distinct_genres", ) # Filter out null/empty values and sort alphabetically @@ -529,13 +520,11 @@ async def get_movie_by_id(id: str): movies_collection = get_collection("movies") try: movie = await movies_collection.find_one({"_id": object_id}) - except Exception as e: - return JSONResponse( - status_code=500, - content=create_error_response( - message=f"Database error occurred: {str(e)}", - code="DATABASE_ERROR" - ) + except Exception: + return server_error_response( + "Database error occurred.", + "DATABASE_ERROR", + log_context="get_movie_by_id", ) @@ -619,29 +608,27 @@ async def get_all_movies( try: result = movies_collection.find(filter_dict).sort(sort).skip(skip).limit(limit) - except Exception as e: - return JSONResponse( - status_code=500, - content=create_error_response( - message=f"An error occurred while fetching movies. {str(e)}", - code="DATABASE_ERROR" - ) - ) - - movies = [] - async for movie in result: - if "title" in movie: - movie["_id"] = str(movie["_id"]) # Convert ObjectId to string - # Ensure that the year field contains int value. - if "year" in movie and not isinstance(movie["year"], int): - cleaned_year = re.sub(r"\D", "", str(movie["year"])) - try: - movie["year"] = int(cleaned_year) if cleaned_year else None - except ValueError: - movie["year"] = None - - movies.append(movie) + movies = [] + + async for movie in result: + if "title" in movie: + movie["_id"] = str(movie["_id"]) # Convert ObjectId to string + # Ensure that the year field contains int value. + if "year" in movie and not isinstance(movie["year"], int): + cleaned_year = re.sub(r"\D", "", str(movie["year"])) + try: + movie["year"] = int(cleaned_year) if cleaned_year else None + except ValueError: + movie["year"] = None + + movies.append(movie) + except Exception: + return server_error_response( + "An error occurred while fetching movies.", + "DATABASE_ERROR", + log_context="get_all_movies", + ) # Return the results wrapped in a SuccessResponse message = f"Found {len(movies)} movies." @@ -671,13 +658,11 @@ async def create_movie(movie: CreateMovieRequest): movies_collection = get_collection("movies") try: result = await movies_collection.insert_one(movie_data) - except Exception as e: - return JSONResponse( - status_code=500, - content=create_error_response( - message=f"Database error occurred: {str(e)}", - code="DATABASE_ERROR" - ) + except Exception: + return server_error_response( + "Database error occurred.", + "DATABASE_ERROR", + log_context="create_movie_insert", ) # Verify that the document was created before querying it @@ -693,13 +678,11 @@ async def create_movie(movie: CreateMovieRequest): try: # Retrieve the created document to return complete data created_movie = await movies_collection.find_one({"_id": result.inserted_id}) - except Exception as e: - return JSONResponse( - status_code=500, - content=create_error_response( - message=f"Database error occurred: {str(e)}", - code="DATABASE_ERROR" - ) + except Exception: + return server_error_response( + "Database error occurred.", + "DATABASE_ERROR", + log_context="create_movie_fetch", ) if created_movie is None: @@ -777,13 +760,11 @@ async def create_movies_batch(movies: List[CreateMovieRequest]) ->SuccessRespons }, f"Successfully created {len(result.inserted_ids)} movies." ) - except Exception as e: - return JSONResponse( - status_code=500, - content=create_error_response( - message=f"Database error occurred: {str(e)}", - code="DATABASE_ERROR" - ) + except Exception: + return server_error_response( + "Database error occurred.", + "DATABASE_ERROR", + log_context="create_movies_batch", ) """ @@ -843,13 +824,11 @@ async def update_movie( {"_id": movie_id}, {"$set":update_dict} ) - except Exception as e: - return JSONResponse( - status_code=500, - content=create_error_response( - message=f"An error occurred while updating the movie: {str(e)}", - code="DATABASE_ERROR" - ) + except Exception: + return server_error_response( + "An error occurred while updating the movie.", + "DATABASE_ERROR", + log_context="update_movie", ) if result.matched_count == 0: @@ -920,13 +899,11 @@ async def update_movies_batch( try: result = await movies_collection.update_many(filter_data, {"$set": update_data}) - except Exception as e: - return JSONResponse( - status_code=500, - content=create_error_response( - message=f"An error occurred while updating movies: {str(e)}", - code="DATABASE_ERROR" - ) + except Exception: + return server_error_response( + "An error occurred while updating movies.", + "DATABASE_ERROR", + log_context="update_movies_batch", ) return create_success_response({ @@ -968,13 +945,11 @@ async def delete_movie_by_id(id: str): try: # Use deleteOne() to remove a single document result = await movies_collection.delete_one({"_id": object_id}) - except Exception as e: - return JSONResponse( - status_code=500, - content=create_error_response( - message=f"Database error occurred: {str(e)}", - code="DATABASE_ERROR" - ) + except Exception: + return server_error_response( + "Database error occurred.", + "DATABASE_ERROR", + log_context="delete_movie_by_id", ) if result.deleted_count == 0: @@ -1043,13 +1018,11 @@ async def delete_movies_batch(request_body: dict = Body(...)) -> SuccessResponse try: result = await movies_collection.delete_many(filter_data) - except Exception as e: - return JSONResponse( - status_code=500, - content=create_error_response( - message=f"An error occurred while deleting movies: {str(e)}", - code="DATABASE_ERROR" - ) + except Exception: + return server_error_response( + "An error occurred while deleting movies.", + "DATABASE_ERROR", + log_context="delete_movies_batch", ) return create_success_response( @@ -1092,13 +1065,11 @@ async def find_and_delete_movie(id: str): # or ensure the document exists before deletion try: deleted_movie = await movies_collection.find_one_and_delete({"_id": object_id}) - except Exception as e: - return JSONResponse( - status_code=500, - content=create_error_response( - message=f"Database error occurred: {str(e)}", - code="DATABASE_ERROR" - ) + except Exception: + return server_error_response( + "Database error occurred.", + "DATABASE_ERROR", + log_context="find_and_delete_movie", ) if deleted_movie is None: @@ -1251,13 +1222,11 @@ async def aggregate_movies_recent_commented( # Execute the aggregation try: results = await execute_aggregation(pipeline) - except Exception as e: - return JSONResponse( - status_code=500, - content=create_error_response( - message=f"Database error occurred during aggregation: {str(e)}", - code="DATABASE_ERROR" - ) + except Exception: + return server_error_response( + "Database error occurred during aggregation.", + "DATABASE_ERROR", + log_context="aggregate_movies_recent_commented", ) # Convert ObjectId to string for response @@ -1387,13 +1356,11 @@ async def aggregate_movies_by_year(): # Execute the aggregation try: results = await execute_aggregation(pipeline) - except Exception as e: - return JSONResponse( - status_code=500, - content=create_error_response( - message=f"Database error occurred during aggregation: {str(e)}", - code="DATABASE_ERROR" - ) + except Exception: + return server_error_response( + "Database error occurred during aggregation.", + "DATABASE_ERROR", + log_context="aggregate_movies_by_year", ) return create_success_response( @@ -1491,13 +1458,11 @@ async def aggregate_directors_most_movies( # Execute the aggregation try: results = await execute_aggregation(pipeline) - except Exception as e: - return JSONResponse( - status_code=500, - content=create_error_response( - message=f"Database error occurred during aggregation: {str(e)}", - code="DATABASE_ERROR" - ) + except Exception: + return server_error_response( + "Database error occurred during aggregation.", + "DATABASE_ERROR", + log_context="aggregate_directors_most_movies", ) return create_success_response( @@ -1584,22 +1549,23 @@ def get_embedding(data, input_type = "document", client=None): data, model = model, output_dimension = outputDimension, input_type = input_type ).embeddings return embeddings[0] - except voyage_error.AuthenticationError as e: + except voyage_error.AuthenticationError: # Handle authentication errors (401) from Voyage AI SDK + logger.exception("Voyage AI authentication failed") raise VoyageAuthError("Invalid Voyage AI API key. Please check your VOYAGE_API_KEY in the .env file") - except voyage_error.InvalidRequestError as e: - # Handle invalid request errors (400) - often due to malformed API key - raise VoyageAPIError(f"Invalid request to Voyage AI API: {str(e)}", 400) - except voyage_error.RateLimitError as e: - # Handle rate limiting errors (429) - raise VoyageAPIError(f"Voyage AI API rate limit exceeded: {str(e)}", 429) - except voyage_error.ServiceUnavailableError as e: - # Handle service unavailable errors (502, 503, 504) - raise VoyageAPIError(f"Voyage AI service unavailable: {str(e)}", 503) + except voyage_error.InvalidRequestError: + logger.exception("Voyage AI invalid request") + raise VoyageAPIError("Invalid request to Voyage AI API.", 400) + except voyage_error.RateLimitError: + logger.exception("Voyage AI rate limit") + raise VoyageAPIError("Voyage AI API rate limit exceeded.", 429) + except voyage_error.ServiceUnavailableError: + logger.exception("Voyage AI service unavailable") + raise VoyageAPIError("Voyage AI service unavailable.", 503) except voyage_error.VoyageError as e: - # Handle any other Voyage AI SDK errors - raise VoyageAPIError(f"Voyage AI API error: {str(e)}", getattr(e, 'http_status', 500) or 500) - except Exception as e: - # Handle unexpected errors - raise VoyageAPIError(f"Failed to generate embedding: {str(e)}", 500) + logger.exception("Voyage AI API error") + raise VoyageAPIError("Voyage AI API error.", getattr(e, "http_status", 500) or 500) + except Exception: + logger.exception("Failed to generate embedding") + raise VoyageAPIError("Failed to generate embedding.", 500) diff --git a/mflix/server/python-fastapi/src/utils/errorResponse.py b/mflix/server/python-fastapi/src/utils/errorResponse.py index d82e144..30e4baa 100644 --- a/mflix/server/python-fastapi/src/utils/errorResponse.py +++ b/mflix/server/python-fastapi/src/utils/errorResponse.py @@ -8,6 +8,10 @@ from datetime import datetime, timezone from typing import Optional, Any +from fastapi.responses import JSONResponse + +from src.utils.logger import logger + def create_error_response( message: str, @@ -36,3 +40,21 @@ def create_error_response( "timestamp": datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z') } + +def server_error_response( + message: str, + code: str, + *, + log_context: str, + status_code: int = 500, +) -> JSONResponse: + """ + Log the current exception and return a generic error payload (no stack traces). + Call only from an except block. + """ + logger.exception("%s failed", log_context) + return JSONResponse( + status_code=status_code, + content=create_error_response(message=message, code=code), + ) + diff --git a/mflix/server/python-fastapi/tests/test_movie_routes.py b/mflix/server/python-fastapi/tests/test_movie_routes.py index b9f12f8..e4bedc2 100644 --- a/mflix/server/python-fastapi/tests/test_movie_routes.py +++ b/mflix/server/python-fastapi/tests/test_movie_routes.py @@ -422,6 +422,27 @@ async def test_get_all_movies_database_error(self, mock_get_collection): assert body["success"] is False assert body["error"]["code"] == "DATABASE_ERROR" + @patch('src.routers.movies.get_collection') + async def test_get_all_movies_cursor_iteration_error(self, mock_get_collection): + """Should return error when cursor iteration fails.""" + mock_collection = MagicMock() + mock_cursor = MagicMock() + mock_cursor.sort.return_value = mock_cursor + mock_cursor.skip.return_value = mock_cursor + mock_cursor.limit.return_value = mock_cursor + mock_cursor.__aiter__.side_effect = Exception("Cursor iteration failed") + mock_collection.find.return_value = mock_cursor + mock_get_collection.return_value = mock_collection + + from src.routers.movies import get_all_movies + response = await get_all_movies() + + assert isinstance(response, JSONResponse) + assert response.status_code == 500 + body = json.loads(response.body.decode()) + assert body["success"] is False + assert body["error"]["code"] == "DATABASE_ERROR" + @pytest.mark.unit @pytest.mark.asyncio